how to run a tunnel in the background as part of a shell script

I run these two commands all the time to connect to my rds instance on aws that's protected behind a firewall (so i tunnel through the ec2 instance) like so:

command 1: open the tunnel (run on background)

ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub &

command 2: connect to db through tunnel port:

PGPASSWORD=password psql dbname -U user -h ip_address -p port;

which is awesome, but I would like to put both these in a single function. But nothing worked with me:

attempt 1: run without background stuff:

function db()
{
    ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub &
    PGPASSWORD=password psql dbname -U user -h ip_address -p port;
}

simply tells me this:

$proddb
[1] 62924
psql: could not connect to server: Connection refused
    Is the server running on host "127.0.0.1" and accepting
    TCP/IP connections on port 6666?

although the initial command is running in the background:

ps aux | grep host
(standard input):435:abdullah         62924   0.0  0.0  4315660   5828 s006  S     3:06PM   0:00.03 ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub

and if i immediately run the next command after it.. i connect to the db just fine!

PGPASSWORD=password psql dbname -U user -h ip_address -p port;
user=>

how do I make this work?


Solution 1:

The first command didn't have time to establish a tunnel when the second command was run, thus giving a "Connection refused".

Just don't use & but use instead the option -f:

-f
Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm.
If the ExitOnForwardFailure configuration option is set to “yes”, then a client started with -f will wait for all remote port forwards to be successfully established before placing itself in the background.

Putting all this together, replace the ssh line in the function with:

ssh -o ExitOnForwardFailure=yes -f -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub

that way running the function multiple times, won't leave (multiple-1) useless ssh running.

It's also possible to replace -N with a short remote sleep command. That way there won't be a long lived idle ssh command that has to be searched if it has to be killed. Ssh will still wait the end of tunnel usage before ending, so the short delay is not an issue:

ssh -o ExitOnForwardFailure=yes -f -L port:host:5432 user@$ip -i ~/.ssh/key.pub sleep 15