1

I want to automate a database backup with help from expect: First, the script establishes a forwarded port from the remote database port to my local machine using ssh. Then I use the database utility to dump all databases.

Therefore, I have the following expect script inside a bash script:

#!/bin/sh

expect <<- DONE
  set timeout -1

  # port-forward remote database port 3306 to local port 3307
  spawn ssh -M -S /var/run/examplecom-mysql-socket -fnNT -L 3307:localhost:3306 someuser@example.com

  expect {
    # conditional prompt appears in case ssh-agent is not active
    "*.ssh/id_rsa':*" {
      send -- "$SSH_PASSPHRASE\r"
    }
    # ?!?!?!? What to expect here in case ssh-agent is active, therefore no password needed, and therefore no prompt appears?
  }

  # dump remote database by connecting to forwarded port
  spawn mysqldump --all-databases --user=root --password --protocol=TCP --host=localhost --port=3307 --verbose --result-file=${DB_DUMP_FILE}

  expect {
    # prompt appears for database password 
    "*?asswor?:*" {
    send -- "$PASSWORD_MYSQL_ROOT\r"      
    }
  }

  expect eof
DONE

This script may run within two scenarios:

  • Either ssh-agent is currently not active. In that case, the spawned ssh process will prompt Enter passphrase for key '${HOME}/.ssh/id_rsa':, which the first expect rule happily matches, and correctly sends the SSH passphrase.
  • Or ssh-agent is currently active. In that case, the spawned ssh process silently establishes the port forwarding and does not need or prompt for the SSH passphrase.

I am struggling to get both scenarios working in my expect script. In particular, I don't know how to modify the script so that the "silent" active-ssh-agent is "expected" and therefore the expect script can continue with starting the database dump.

I tried the expect multiple things approach (at above ?!?!?!? comment location) by trying out:

  • expecting a newline,
  • expecting my shell prompt,
  • expecting eof.

But none worked. I found similar problems and suggested solutions around StackExchange: * How to use expect with optional prompts? * https://superuser.com/q/412259/137881 * TCL / Expect Scripting - Using a conditional statement to attempt a secondary login password

But the solutinos seem to cause code duplications: Each conditional prompt means copy and pasting the rest of the script, which makes the script get convoluted quickly with nested conditional prompts for each ssh statement.

How can I have expect work gracefully in situations where ssh does not prompt for the passphrase/password because of an active ssh-agent?

Community
  • 1
  • 1
Abdull
  • 26,371
  • 26
  • 130
  • 172

1 Answers1

2

Yeah, that's a funny one. Since you're using ssh -N, you don't launch any process on the remote host, and the ssh session just sits there. You would just have to sleep for a couple of seconds and hope the tunnel gets established.

set timeout 2
spawn ssh -M -S /var/run/examplecom-mysql-socket -fnNT -L 3307:localhost:3306 someuser@example.com

expect {
    # conditional prompt appears in case ssh-agent is not active
    "*.ssh/id_rsa':*" { send -- "$SSH_PASSPHRASE\r" }
    # wait until the timeout, then carry on with the rest of the script
    timeout
}

set timeout -1
# ...

Or, remove -N, then you can expect to see the prompt on the remote host.

spawn ssh -M -S /var/run/examplecom-mysql-socket -fnT -L 3307:localhost:3306 someuser@example.com
# ..........................................^^^^

expect {
    # conditional prompt appears in case ssh-agent is not active
    "*.ssh/id_rsa':*" { send -- "$SSH_PASSPHRASE\r" }
    # I assume your prompt ends with a dollar sign and a space
    -re {\$ $}
}

Now you can spawn the mysql command and interact with that.

Abdull
  • 26,371
  • 26
  • 130
  • 172
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Awesome! Your `timeout` suggestion did the trick. The answer version from 2016-03-04 relies on the assumption that the port forward will definitely be established before timeout. To not depend on this assumption, I have expanded it with `timeout { if {[file exists /var/run/examplecom-mysql-socket]} { send_user "file exists... continuing\r" } else { send_user "file does not exist. This implies ssh port could not be established. Quitting script\r" exit 2 }`, thereby quitting in case the socket (file) `/var/run/examplecom-mysql-socket` wasn't established. – Abdull Mar 07 '16 at 14:55