85

I have a sample sh script on my Linux environment, which basically run's the ssh-agent for the current shell, adds a key to it and runs two git commands:

#!/bin/bash
eval "$(ssh-agent -s)"
ssh-add /home/duvdevan/.ssh/id_rsa

git -C /var/www/duvdevan/ reset --hard origin/master
git -C /var/www/duvdevan/ pull origin master

Script actually works fine, but every time I run it I get a new process so I think it might become a performance issue and I might end up having useless processes out there.

An example of the output:

Agent pid 12109
Identity added: /home/duvdevan/.ssh/custom_rsa (rsa w/o comment)

Also, along with all this, is it possible to find an existing ssh-agent process and add my keys into it?

user229044
  • 232,980
  • 40
  • 330
  • 338
Zlatan Omerović
  • 3,863
  • 4
  • 39
  • 67
  • 4
    Try `$SSH_AGENT_PID`. – choroba Nov 11 '16 at 13:50
  • If you want to kill a process after spawning it, you can store its PID into a variable and call it like so: `kill -9 $PID_SSH_AGENT` – alok Nov 11 '16 at 13:57
  • I think making the script responsible for starting an agent is the wrong approach. Just assume that an agent *is* running, and require any user to ensure that they have an agent already (usually started by your initial login shell.) – chepner Nov 11 '16 at 14:28
  • You could also simply run such a script with `ssh-agent my-script` to start an agent that exits as soon as `my-script` exits. – chepner Nov 11 '16 at 14:29
  • 1
    I cannot do that since login to the machine multiple times a day - and not just me, but other people as well. – Zlatan Omerović Nov 11 '16 at 14:29
  • Kill the agent when you logout, or use agent-forwarding to only run the agent on your local machine if you are ssh-ing into the machine. – chepner Nov 11 '16 at 14:30
  • if you kill the agent process and if the bash session is still open, the variable $SSH_AGENT_PID still holds the pid information. make sure to clear this variable after killing the process. – MichaelHuelsen Jul 28 '17 at 07:13
  • Another variant would be to check if there are any agent processes running via `ps aux | grep ssh-agent`. If you pipe this further to `| wc -l` and this equals "0", there are no running processes anymore. – MichaelHuelsen Jul 28 '17 at 07:16
  • ps -p $SSH_AGENT_PID > /dev/null || eval "$(ssh-agent -s)" – iceman Feb 15 '19 at 09:02
  • _You'd think the agent itself will have a built-in option to only run a new process if not already running... **but no, we have to write some complex scripts!**_ – ADTC Nov 27 '21 at 18:56

14 Answers14

78

No, really, how to check if ssh-agent is already running in bash?

Answers so far don't appear to answer the original question...

Here's what works for me:

if ps -p $SSH_AGENT_PID > /dev/null
then
   echo "ssh-agent is already running"
   # Do something knowing the pid exists, i.e. the process with $PID is running
else
eval `ssh-agent -s`
fi

This was taken from here

AndrewD
  • 4,924
  • 3
  • 30
  • 32
  • 7
    **Usually does not work**. Neither in graphical sessions (where `ssh-agent` runs locally or is built into the key manager) or session with `ssh -A`, where the `ssh-agent` runs locally. The correct way can be found in [the answer of idbrii](https://stackoverflow.com/a/48509425/490291) – Tino May 21 '19 at 16:56
  • 1
    Can confirm `SSH_AGENT_PID` is unreliable. On my Mac (High Sierra) and SSH `OpenSSH_7.8p1`, when invoking ssh connection directly, e.g. `ssh host`, the agent is started with `SSH_AUTH_SOCK` but **not** `SSH_AGENT_PID` – Alexander Kucheryuk Dec 26 '20 at 00:30
59

Also, along with all this, is it possible to find an existing ssh-agent process and add my keys into it?

Yes. We can store the connection info in a file:

# Ensure agent is running
ssh-add -l &>/dev/null
if [ "$?" == 2 ]; then
    # Could not open a connection to your authentication agent.

    # Load stored agent connection info.
    test -r ~/.ssh-agent && \
        eval "$(<~/.ssh-agent)" >/dev/null

    ssh-add -l &>/dev/null
    if [ "$?" == 2 ]; then
        # Start agent and store agent connection info.
        (umask 066; ssh-agent > ~/.ssh-agent)
        eval "$(<~/.ssh-agent)" >/dev/null
    fi
fi

# Load identities
ssh-add -l &>/dev/null
if [ "$?" == 1 ]; then
    # The agent has no identities.
    # Time to add one.
    ssh-add -t 4h
fi

This code is from pitfalls of ssh agents which describes both the pitfalls of what you're currently doing, of this approach, and how you should use ssh-ident to do this for you.


If you only want to run ssh-agent if it's not running and do nothing otherwise:

if [ $(ps ax | grep [s]sh-agent | wc -l) -gt 0 ] ; then
    echo "ssh-agent is already running"
else
    eval $(ssh-agent -s)
    if [ "$(ssh-add -l)" == "The agent has no identities." ] ; then
        ssh-add ~/.ssh/id_rsa
    fi

    # Don't leave extra agents around: kill it on exit. You may not want this part.
    trap "ssh-agent -k" exit
fi

However, this doesn't ensure ssh-agent will be accessible (just because it's running doesn't mean we have $SSH_AGENT_PID for ssh-add to connect to).

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
idbrii
  • 10,975
  • 5
  • 66
  • 107
  • 8
    **This should be the accepted answer.** At least the first half, because `ssh-add -l` is the right way to test for a live agent ([I'd rather suggest `timeout 0.3 ssh-add -l`](https://gist.github.com/hilbix/7d3bacb1b91cdf6df5c6) because `ssh-add` can hang on starved `ssh`-connections - i.E. in `tmux`). Your first script works on KDE or remote sessions with `ssh -A`. However the second half is more or less useless like all the other answers here, as often there is no `ssh-agent` running locally. BTW: Why the `[s]` in `grep [s]sh-agent` and not `grep -F ssh-agent` (which spares some cycles). – Tino May 21 '19 at 17:41
  • 3
    "as often there is no ssh-agent running locally" -- except in OP's case where their problem was multiple ssh-agents. "Why the [s]" -- `ps ax|grep -F ssh-agent` will return the grep process because ps output includes program arguments (try it). Using the character class in the regex will prevent grep from matching itself. See also https://stackoverflow.com/a/9375940/79125 – idbrii May 22 '19 at 16:54
  • 1
    This is also a cool answer because it tells "how to tell that ssh-agent is dead" or "how to figure out whether ssh-agent is running without knowing the pid". Makes sense when one has `screen` or `tmux` in conjunction with agent forwarding. – Dallaylaen Aug 24 '19 at 21:19
  • I never considered doing an `eval "$(<~/.ssh-agent)"` and would rather `source ~/.ssh-agent`. Just curious if it makes any difference. – chutz Apr 27 '21 at 05:31
  • 1
    @chutz: eval is suggested by the ssh-agent docs (likely because they don't use an intermediate file), but I'm not sure if there's a difference. That might make a good question (closest I found was [this one](https://askubuntu.com/q/174721/9411)). – idbrii Apr 27 '21 at 22:33
  • my one-liner in zsh: `if [[ $(ssh-add -l &>/dev/null)? -eq 2 ]] ; then eval $(ssh-agent); fi` – Cody Mar 23 '22 at 20:11
11

If you want it to be killed right after the script exits, you can just add this after the eval line:

trap "kill $SSH_AGENT_PID" exit

Or:

trap "ssh-agent -k" exit

$SSH_AGENT_PID gets set in the eval of ssh-agent -s.

You should be able to find running ssh-agents by scanning through /tmp/ssh-* and reconstruct the SSH_AGENT variables from it (SSH_AUTH_SOCK and SSH_AGENT_PID).

Bazer Con
  • 105
  • 4
Friek
  • 1,533
  • 11
  • 13
  • Why shouldn't I just add `kill -9 $SSH_AGENT_PID` at the end of my script, as @alok said in his comment on the question? – Zlatan Omerović Nov 11 '16 at 14:18
  • 2
    If the script itself gets killed (with an interruptable signal) while running, that command won't be run. With the trap it will. – Friek Nov 11 '16 at 14:21
  • Also, `kill -9` should never be necessary except for killing a bugging program during development. `kill` by itself should be sufficient in almost every instance. – chepner Nov 11 '16 at 14:22
  • Yes. I've just seen that `ssh-agent -k` kills the process and unsets `SSH_AUTH_SOCK` and `SSH_AGENT_PID` variables - I'm using your solution with `trap`. Thanks! – Zlatan Omerović Nov 11 '16 at 14:25
10

ps -p $SSH_AGENT_PID > /dev/null || eval "$(ssh-agent -s)"

Single line command. Run for the first time will start ssh-agent. Run for the second time will not start the ssh-agent. Simple and Elegant Mate !!!

iceman
  • 181
  • 1
  • 7
  • 1
    Not bullet proof but quite elegant solution :) – victmo Feb 10 '22 at 17:34
  • 2
    In some systems you might get a warning if `$SSH_AGENT_PID` is not set. Also you will get a message after running `eval ssh-agent -s`. I modified the above to make it completely silent: `ps -p "000$SSH_AGENT_PID" > /dev/null || eval "$(ssh-agent -s) > /dev/null"` – victmo Feb 10 '22 at 17:37
6

Using $SSH_AGENT_PID can only test the ssh-agent but miss identities when it is not yet added

$ eval `ssh-agent`
Agent pid 9906
$ echo $SSH_AGENT_PID
9906
$ ssh-add -l
The agent has no identities.

So it would be save to check it with ssh-add -l with an expect script like example below:

$ eval `ssh-agent -k`
Agent pid 9906 killed
$ ssh-add -l
Could not open a connection to your authentication agent.
$ ssh-add -l &>/dev/null
$ [[ "$?" == 2 ]] && eval `ssh-agent`
Agent pid 9547
$ ssh-add -l &>/dev/null
$ [[ "$?" == 1 ]] && expect $HOME/.ssh/agent
spawn ssh-add /home/user/.ssh/id_rsa
Enter passphrase for /home/user/.ssh/id_rsa: 
Identity added: /home/user/.ssh/id_rsa (/home/user/.ssh/id_rsa)
$ ssh-add -l
4096 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /home/user/.ssh/id_rsa (RSA)

So when both ssh-agent and ssh-add -l are put to run on a bash script:

#!/bin/bash
ssh-add -l &>/dev/null
[[ "$?" == 2 ]] && eval `ssh-agent`
ssh-add -l &>/dev/null
[[ "$?" == 1 ]] && expect $HOME/.ssh/agent

then it would always check and assuring that the connection is running:

$ ssh-add -l
4096 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /home/user/.ssh/id_rsa (RSA)

You can also emulate the repeating of commands on above script with do while

eQ19
  • 9,880
  • 3
  • 65
  • 77
3

The accepted answer did not work for me under Ubuntu 14.04.

The test to check if the ssh-agent is running I have to use is:

[[ ! -z ${SSH_AGENT_PID+x} ]]

And I am starting the ssh-agent with:

exec ssh-agent bash

Otherwise the SSH_AGENT_PID is not set.

The following seems to work under both Ubuntu 14.04 and 18.04.

#!/bin/bash
sshkey=id_rsa
# Check ssh-agent
if [[ ! -z ${SSH_AGENT_PID+x} ]]
then
    echo "[OK] ssh-agent is already running with pid: "${SSH_AGENT_PID}
else
    echo "Starting new ssh-agent..."
    `exec ssh-agent bash`
    echo "Started agent with pid: "${SSH_AGENT_PID}
fi
# Check ssh-key
if [[ $(ssh-add -L | grep ${sshkey} | wc -l) -gt 0 ]]
then
    echo "[OK] SSH key already added to ssh-agent"
else
    echo "Need to add SSH key to ssh-agent..."
    # This should prompt for your passphrase
    ssh-add ~/.ssh/${sshkey}
fi
SveborK
  • 57
  • 4
2

Thanks to all the answers here. I've used this thread a few times over the years to tweak my approach. Wanted to share my current ssh-agent.sh checker/launcher script that works for me on Linux and OSX.

The following block is my $HOME/.bash.d/ssh-agent.sh

function check_ssh_agent() {
  if [ -f $HOME/.ssh-agent ]; then
    source $HOME/.ssh-agent > /dev/null
  else
    # no agent file
    return 1
  fi

  if [[ ${OSTYPE//[0-9.]/} == 'darwin' ]]; then
    ps -p $SSH_AGENT_PID > /dev/null  
    # gotcha: does not verify the PID is actually an ssh-agent
    # just that the PID is running
    return $?
  fi

  if [ -d /proc/$SSH_AGENT_PID/ ]; then
    # verify PID dir is actually an agent
    grep ssh-agent /proc/$SSH_AGENT_PID/cmdline  > /dev/null  2> /dev/null; 
    if [ $? -eq 0 ]; then
      # yep - that is an agent
      return 0
    else
      # nope - that is something else reusing the PID
      return 1
    fi
  else
    # agent PID dir does not exist - dead agent
    return 1
  fi 
}

function launch_ssh_agent() {
  ssh-agent > $HOME/.ssh-agent
  source $HOME/.ssh-agent
  # load up all the pub keys
  for I in $HOME/.ssh/*.pub ; do
    echo adding ${I/.pub/}
    ssh-add ${I/.pub/}
  done
}

check_ssh_agent
if [ $? -eq 1 ];then
  launch_ssh_agent
fi

I launch the above from my .bashrc using:

if [ -d $HOME/.bash.d ]; then
  for I in $HOME/.bash.d/*.sh; do
    source $I  
  done
fi

Hope this helps others get up and going quickly.

Created a public gist if you want to hack/improve this with me: https://gist.github.com/dayne/a97a258b487ed4d5e9777b61917f0a72

1
cat /usr/local/bin/ssh-agent-pro << 'EOF'
#!/usr/bin/env bash
SSH_AUTH_CONST_SOCK="/var/run/ssh-agent.sock"

if [[ x$(wc -w <<< $(pidof ssh-agent)) != x1 ]] || [[ ! -e ${SSH_AUTH_CONST_SOCK} ]]; then
  kill -9 $(pidof ssh-agent) 2>/dev/null
  rm -rf ${SSH_AUTH_CONST_SOCK}
  ssh-agent -s -a ${SSH_AUTH_CONST_SOCK} 1>/dev/null
fi

echo "export SSH_AUTH_SOCK=${SSH_AUTH_CONST_SOCK}"
echo "export SSH_AGENT_PID=$(pidof ssh-agent)"
EOF
echo "eval \$(/usr/local/bin/ssh-agent-pro)" >> /etc/profile
. /etc/profile

then you can ssh-add xxxx once, you can use ssh-agent everytime when you login.

zwtop
  • 143
  • 1
  • 8
1

I've noticed that having a running agent is not enough because sometimes, the SSH_AUTH_SOCK variable is set or pointing to a socket file that does not exist anymore.

Therefore, to connect to an already running ssh-agent on your machine, you can do this :

$ pgrep -u $USER -n ssh-agent -a
1906647 ssh-agent -s
$ ssh-add -l
Could not open a connection to your authentication agent.
$ test -z "$SSH_AGENT_PID" && export SSH_AGENT_PID=$(pgrep -u $USER -n ssh-agent)
$ test -z "$SSH_AUTH_SOCK" && export SSH_AUTH_SOCK=$(ls /tmp/ssh-*/agent.$(($SSH_AGENT_PID-1)))
$ ssh-add -l
The agent has no identities.
SebMa
  • 4,037
  • 29
  • 39
  • 1
    This is nice and neat trick that solves the problem to pair agent PID to existing socket! Best answer here. – bubak Nov 20 '21 at 20:48
1

Regarding finding running ssh-agents, previous answers either don't work or rely on a magic file like $HOME/.ssh_agent. These approaches require us to believe that user never run agents without saving their output to this file.

My approach instead relies on a rarely changed default UNIX domain socket template to find an accessible ssh-agent among available possibilities.

# (Paste the below code to your ~/.bash_profile and ~/.bashrc files)
C=$SSH_AUTH_SOCK
R=n/a
unset SSH_AUTH_SOCK
for s in $(ls $C /tmp/ssh-*/agent.* 2>/dev/null | sort -u) ; do
  if SSH_AUTH_SOCK=$s ssh-add -l >/dev/null ; then R=$? ; else R=$? ; fi
  case "$R" in
    0|1) export SSH_AUTH_SOCK=$s ; break ;;
  esac
done
if ! test -S "$SSH_AUTH_SOCK" ; then
  eval $(ssh-agent -s)
  unset SSH_AGENT_PID
  R=1
fi
echo "Using $SSH_AUTH_SOCK"
if test "$R" = "1" ; then
  ssh-add
fi

In this approach, SSH_AGENT_PID remains unknown, since it is hard to deduce it for non-roots. I assume it is actually not required for users since they don't normally want to stop agents. On my system, setting SSH_AUTH_SOCK is enough to communicate with agent for e.g. passwordless authentication. The code should work with any shell-compatible shell.

Grwlf
  • 896
  • 9
  • 21
0

You can modify line #1 to:

PID_SSH_AGENT=`eval ssh-agent -s | grep -Po "(?<=pid\ ).*(?=\;)"`

And then at the end of the script you can do:

kill -9 $PID_SSH_AGENT
alok
  • 502
  • 3
  • 15
  • 2
    Firstly, $varname references a variable, it can't be set that way. Secondly, why would you want to do that if `eval ssh-agent` already sets $SSH_AGENT_PID? – Friek Nov 11 '16 at 14:14
  • Sorry, did not know that it sets that variable. And yes, $ should not have been there. Thanks. – alok Nov 11 '16 at 14:16
0

I made this bash function to count and return the number of running ssh-agent processes... it searches ssh-agent process using procfs instead of using $ ps -p $SSH_AGENT_PID:cmd or $SSH_AUTH_SOCK:var ... (these ENV-var. can still be set with old values while ssh-agent's process is already killed: if $ ssh-agent -k or $ $(ssh-agent -k) instead of $ eval $(ssh-agent -k))

function count_agent_procfs(){
    declare -a agent_list=( ) 
    for folders in $(ls -d /proc/*[[:digit:]] | grep -v /proc/1$);do
        fichier="${folders}/stat"
        pid=${folders/\/proc\//}
        [[ -f ${fichier} ]] && [[ $(cat ${fichier} | cut -d " " -f2) == "(ssh-agent)" ]] && agent_list+=(${pid})
    done
    return ${#agent_list[@]}
}

..and then if there is a lot of ssh-agent process running you get their PID with this list..."${agent_list[@]}"

trollo974
  • 1
  • 1
0

Very simple command to check how many processes are running for ssh-agent (or any other program): pidof ssh-agent or: pgrep ssh-agent

And very simple command to kill all processes of ssh-agent (or any program): kill $(pidof ssh-agent)

0

The answer from AndrewD was the most helpful for me in most cases, but I found out that when $SSH_AGENT_PID is not set or present, the ps command will respond with the error:

error: list of process IDs must follow -p

To fix this, we need to check the existence of the $SSH_AGENT_PID variable so that the condition case will now be:

if [ -n "$SSH_AGENT_PID" ] && ps -p "$SSH_AGENT_PID" > /dev/null

The entire modified snippet:

if [ -n "$SSH_AGENT_PID" ] && ps -p "$SSH_AGENT_PID" > /dev/null
then
   echo "ssh-agent is already running"
   # Do something knowing the pid exists, i.e. the process with $PID is running
else
eval `ssh-agent -s`
fi

Shorter and revised version:

if ! [ -n "$SSH_AGENT_PID" ] || ! ps -p "$SSH_AGENT_PID" > /dev/null
then
    eval "$(ssh-agent -s)"
fi

ssh-add ~/.ssh/github_ed25519
Zlatan Omerović
  • 3,863
  • 4
  • 39
  • 67