Try to connect via script to multiple servers - bash

I am trying to connect to multiple servers from file (servers.txt --> something around 300 IP add) and some server is RHEL5, some RHEL7 (so I must use a diff command).
I can connect to multiple server, its OK, but I can't continue with some condition: like if you don't know one command use another command.
#!/bin/bash
for host in $(cat servers.txt); do
ssh -q -o StrictHostKeyChecking=no root#$host
#when I try to continue with "if" -> of course I am logout from servers

I agree with #rkosegi that this can be achieved an Ansible ad-hoc command, but you'd have to convert the list of servers to an inventory - a very simple task.
At the bash prompt, I think I understand what you want. You want to try multiple commands via each ssh command. So let's assume you want to check the version of the redhat-release package and take different actions from that:
while read HOST; do
ssh -q -o StrictHostKeyChecking=no root#$HOST '
VERSION="`rpm -q --queryformat "%{VERSION}" redhat-release`"
if [[ $VERSION == 5* ]]; then # RHEL5
echo this is a rhel5
elif [[ $VERSION == 7* ]]; then # RHEL7
echo this is a rhel7
else
echo this is neither a rhel5 or rhel7
fi
'
done <servers.txt
Of course, the script can be written on one line, but I thought I'd format it nicer here for increased readability.
Note: The post being tagged with bash, the above command also uses [[ tests which are specific to bash and won't work on some other shells.

Related

how to run bash script interactively from url? [duplicate]

I have a simple Bash script that takes in inputs and prints a few lines out with that inputs
fortinetTest.sh
read -p "Enter SSC IP: $ip " ip && ip=${ip:-1.1.1.1}
printf "\n"
#check IP validation
if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "SSC IP: $ip"
printf "\n"
else
echo "Enter a valid SSC IP address. Ex. 1.1.1.1"
exit
fi
I tried to upload them into my server, then try to run it via curl
I am not sure why the input prompt never kick in when I use cURL/wget.
Am I missing anything?
With the curl ... | bash form, bash's stdin is reading the script, so stdin is not available for the read command.
Try using a Process Substitution to invoke the remote script like a local file:
bash <( curl -s ... )
Your issue can be simply be reproduced by run the script like below
$ cat test.sh | bash
Enter a valid SSC IP address. Ex. 1.1.1.1
This is because the bash you launch with a pipe is not getting a TTY, when you do a read -p it is read from stdin which is content of the test.sh in this case. So the issue is not with curl. The issue is not reading from the tty
So the fix is to make sure you ready it from tty
read < /dev/tty -p "Enter SSC IP: $ip " ip && ip=${ip:-1.1.1.1}
printf "\n"
#check IP validation
if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "SSC IP: $ip"
printf "\n"
else
echo "Enter a valid SSC IP address. Ex. 1.1.1.1"
exit
fi
Once you do that even curl will start working
vagrant#vagrant:/var/www/html$ curl -s localhost/test.sh | bash
Enter SSC IP: 2.2.2.2
SSC IP: 2.2.2.2
I personally prefer source <(curl -s localhost/test.sh) option. While it is similar to bash ..., the one significant difference is how processes handled.
bash will result in a new process being spun up, and that process will evoke commands from the script.
source on the other hand will use current process to evoke commands from the script.
In some cases that can play a key role. I admit that is not very often though.
To demonstrate do the following:
### Open Two Terminals
# In the first terminal run:
echo "sleep 5" > ./myTest.sh
bash ./myTest.sh
# Switch to the second terminal and run:
ps -efjh
## Repeat the same with _source_ command
# In the first terminal run:
source ./myTest.sh
# Switch to the second terminal and run:
ps -efjh
Results should look similar to this:
Before execution:
Running bash (main + two subprocesses):
Running source (main + one subprocess):
UPDATE:
Difference in use variable usage by bash and source:
source command will use your current environment. Meaning that upon execution all changes and variable declarations, made by the script, will be available in your prompt.
bash on the other hand will be running in as a different process; therefore, all variables will be discarded when process exits.
I think everyone will agree that there are benefits and drawbacks to each method. You just have to decide which one is better for your use case.
## Test for variables declared by the script:
echo "test_var3='Some Other Value'" > ./myTest3.sh
bash ./myTest3.sh
echo $test_var3
source ./myTest3.sh
echo $test_var3
## Test for usability of current environment variables:
test_var="Some Value" # Setting a variable
echo "echo $test_var" > myTest2.sh # Creating a test script
chmod +x ./myTest2.sh # Adding execute permission
## Executing:
. myTest2.sh
bash ./myTest2.sh
source ./myTest2.sh
./myTest2.sh
## All of the above results should print the variable.
I hope this helps.

Multiline SSH remote script

I have been trying for 2 hours to rewrite this SSH remotelly executed command to multiline form to be more readable, but everytime I have something wrong. I tried multiple ways as described in similar questions but I just suck at BASH. I am trying to do
echo "Deploying $1 to remote"
sudo ssh -i ../keys/key.pem username#$2 '
id=$(docker ps -a -q -f name=$1); if [ -n "$id" ]; then docker rm --force $1; fi;
docker run -d --network host -v /var/log/$1/:/var/log/$1/
-e SERVER_PORT=80
--name $1 username/image:$1'
I want to run docker image remotelly with some arguments created remotelly ($id) and some expanded locally before executing the script ($1, $2).
You will probably find this easier to write if you write it as a standalone script:
#!/bin/sh
container="$1"
id=$(docker ps -aq -f name="$container")
if [ -n "$id" ]; then
...
fi
...
Run this by hand on the target system to make sure it does what you want.
Once you have that, you can copy the script and run it in two separate commands:
echo "Deploying $1 to remote"
scp -i ../keys/key.pem launch-container.sh "username#$2:/tmp/launch-container.sh"
ssh -i ../keys/key.pem "username#$2" sh /tmp/launch-container.sh "$1"
You also might look into various system-automation tools that are purpose-built for this kind of task. I'm partial to Ansible for not requiring a dedicated server and working over ssh in the same way you're showing; it has a set of Docker-related commands that can do this set of tasks fairly directly.

bash script parallel ssh remote command

i have a script that fires remote commands on several different machines through ssh connection. Script goes something like:
for server in list; do
echo "output from $server"
ssh to server execute some command
done
The problem with this is evidently the time, as it needs to establish ssh connection, fire command, wait for answer, print it. What i would like is to have script that would try to establish connections all at once and return echo "output from $server" and output of command as soon as it gets it, so not necessary in the list order.
I've been googling this for a while but didn't find an answer. I cannot cancel ssh session after command run as one thread suggested, because i need an output and i cannot use parallel gnu suggested in other threads. Also i cannot use any other tool, i cannot bring/install anything on this machine, only useable tool is GNU bash, version 4.1.2(1)-release.
Another question is how are ssh sessions like this limited? If i simply paste 5+ or so lines of "ssh connect, do some command" it actually doesn't do anything, or execute only on first from list. (it works if i paste 3-4 lines). Thank you
Have you tried this?
for server in list; do
ssh user#server "command" &
done
wait
echo finished
Update: Start subshells:
for server in list; do
(echo "output from $server"; ssh user#server "command"; echo End $server) &
done
wait
echo All subshells finished
There are several parallel SSH tools that can handle that for you:
http://code.google.com/p/pdsh/
http://sourceforge.net/projects/clusterssh/
http://code.google.com/p/sshpt/
http://code.google.com/p/parallel-ssh/
Also, you could be interested in configuration deployment solutions such as Chef, Puppet, Ansible, Fabric, etc (see this summary ).
A third option is to use a terminal broadcast such as pconsole
If you only can use GNU commands, you can write your script like this:
for server in $servers ; do
( { echo "output from $server" ; ssh user#$server "command" ; } | \
sed -e "s/^/$server:/" ) &
done
wait
and then sort the output to reconcile the lines.
I started with the shell hacks mentionned in this thread, then proceeded to something somewhat more robust : https://github.com/bearstech/pussh
It's my daily workhorse, and I basically run anything against 250 servers in 20 seconds (it's actually rate limited otherwise the connection rate kills my ssh-agent). I've been using this for years.
See for yourself from the man page (clone it and run 'man ./pussh.1') : https://github.com/bearstech/pussh/blob/master/pussh.1
Examples
Show all servers rootfs usage in descending order :
pussh -f servers df -h / |grep /dev |sort -rn -k5
Count the number of processors in a cluster :
pussh -f servers grep ^processor /proc/cpuinfo |wc -l
Show the processor models, sorted by occurence :
pussh -f servers sed -ne "s/^model name.*: //p" /proc/cpuinfo |sort |uniq -c
Fetch a list of installed package in one file per host :
pussh -f servers -o packages-for-%h dpkg --get-selections
Mass copy a file tree (broadcast) :
tar czf files.tar.gz ... && pussh -f servers -i files.tar.gz tar -xzC /to/dest
Mass copy several remote file trees (gather) :
pussh -f servers -o '|(mkdir -p %h && tar -xzC %h)' tar -czC /src/path .
Note that the pussh -u feature (upload and execute) was the main reason why I programmed this, no tools seemed to be able to do this. I still wonder if that's the case today.
You may like the parallel-ssh project with the pssh command:
pssh -h servers.txt -l user command
It will output one line per server when the command is successfully executed. With the -P option you can also see the output of the command.

Iteration is not working

I am trying to get this script to work. It works fine when I execute it locally but it's not iterating through IP file for remote servers list in the file.
#!/bin/bash
entry=$(cat IPfile)
for i in $entry
do
ssh -q "$entry"
if [[ -n $(egrep "Red Hat Enterprise Linux Server release 5" /etc/redhat-release) ]]; then
sed -i 's/#includedir/##includedir/' /etc/sudoers
fi
done
Are you sure you don't mean ssh -q "$i"? $entry presumably expands to many values.
Edit
I assume you want the grep/sed to occur on each server in the ip list. What your script does is to ssh into each server, then wait for instructions. This should help.
#!/bin/bash
for ip in $(<IPfile); do
# Tell the remote server to start bash, but since its
# standard input is not a TTY it will start bash in
# noninteractive mode.
ssh -q "$ip" bash <<-SSH
if [ ! -r /etc/redhat-release ]; then
printf 'ip "%s" did not have a redhat-release file.\n' "$ip"
elif fgrep -q 'Red Hat Enterprise Linux Server release 5' /etc/redhat-release; then
sed -i 's/#includedir/##includedir/' /etc/sudoers
else
printf 'ip "%s" was not rhel server 5.\n' "$ip"
fi
SSH
done
Some distros don't remove /etc/redhat-release file and make it as part of their own release package. If the script is intended to run strictly on RHEL5, check the version of redhat-release package instead.
The conditional statement will then be:
if version=$(rpm -q --qf "%{version}" redhat-release); then
# it is RHEL
if [ ${version:0:1} == 5 ]; then
# it is RHEL 5
fi
fi
It's because the ssh -q command is "eating" your list. You should add the "-n" option to ssh to prevent it from getting your list from the standard input.

How can you tell if bash is being accessed from a remote machine?

I am trying to write out a dynamic bash profile for a few machines, and was wondering if there is a variable that allows .bashrc if it is being accessed remotely. I've seen a few examples using X variables, but that is irrelevant to both machines.
if [ "$SSH_CONNECTION" ]; then
echo I am remote
else
echo I am local
fi
When you connect via ssh, your bash process is a child of sshd ($PPID is a variable of bash's parent process - ssh that is, if you connect remotely). You can check for that:
if ps ax | grep ^$PPID'.*sshd' &> /dev/null; then
# do your stuff
fi
Edit: I was bored and used time to get execution times and found out that this version apparently is a couple of milliseconds faster:
if grep ^sshd: /proc/$PPID/cmdline &> /dev/null; then
# do your stuff
fi

Resources