Tuesday, 13 November 2012

ssh-and-tmux: part three

On many of the hosts I connect to using my ssh-and-tmux script I want to be able to use git to connect to repositories via ssh. Initially this is as simple as ensuring that ssh-agent forwarding is enabled by passing -A to ssh. The problem comes when I disconnect and reconnect from somewhere else. This kills the ssh session and the socket used for the agent is removed and SSH_AUTH_SOCK ends up pointing to a file that doesn't exist.

The solution is to use a fixed name for the authentication socket and symlink that name to the real one as it changes.

#!/bin/sh
set -e

if [ -z "$SSH_AUTH_SOCK" ]; then
    echo No ssh agent found. Starting one.
    eval `ssh-agent`
fi

if [ -n "$SSH_AUTH_SOCK" ]; then
    export SSH_AUTH_SOCK_OLD=$SSH_AUTH_SOCK
    export SSH_AUTH_SOCK=$HOME/.ssh/.tmux-agent
    ln -sf "$SSH_AUTH_SOCK_OLD" $SSH_AUTH_SOCK
else
    echo Failed to start ssh agent

    # Set the socket anyway in case someone reattaches later with a
    # valid agent.
    export SSH_AUTH_SOCK=$HOME/.ssh/.tmux-agent
fi

# Use a specific name for the tmux/screen session so
# that we can tell that it's one that uses this scheme.
# We have to detach other clients with tmux so that we
# don't end up with a stale agent in them when we
# return to them later.
if which tmux >/dev/null 2>/dev/null; then
    tmux -2 -L ssh-agent "$@" attach -d || tmux -2 -L ssh-agent "$@"
else
    exec screen -S ssh-agent "$@" -x -RR
fi

Now the original script just needs to run this script on the remote host rather than executing tmux directly.

I've been using this final solution for quite a while now and it works really well.

Monday, 12 November 2012

ssh-and-tmux: part two

Many of the hosts I wish to use my ssh-and-tmux script to connect to are behind firewalls so I can't connect to them directly. I find that it is more reliable to just ssh through a gateway host rather than using any VPN that might be available. This can easily be bolted on to the ssh-and-tmux script provided a good guess can be made as to which network you're actually on:

#!/bin/sh
if [ -n "$TMUX" ]; then
    echo Already in tmux
    exit 1
fi

if [ -n "$STY" ]; then
    echo Already in screen
    exit 1
fi

while true; do
    # First work out where we are based on our IP address.
    # We do this every time round the loop in case we've 
    # moved network since last time.
    addrs=`ip --family inet --oneline addr`

    work_via=gateway.example.com
    home_via=gateway.randombitsofuselessinformation.blogspot.com

    case "$addrs" in
 *192.168.1.*)
     # We're on the home network
     home_via=
     ;;
 *172.16.*)
     # We're on the work network
     work_via=
     ;;
    esac

    extra=
    via=
    case "$1" in
 work-host1)
     via=$work_via
     # Add a port forward for this host too
     extra=-L8080:work-host1:8080
     dest=work-host1
     ;;
 home-host1)
     via=$home_via
     dest=home-host1
     ;;
 home-host2)
     via=$home_via
     dest=home-host2
     ;;
 *.*)
     # Hosts with dots are assumed to be on the
            # Internet at large
     via=
     dest="$1"
     ;;
 *)
     # All other hosts are assumed to be on the work
            # network
     via=$work_via
     dest="$1"
     ;;
    esac
    
    if [ -n "$via" ]; then
 ssh $extra -A -t "$via" \
            ssh -t "$dest" "tmux -2 -L netbook attach \
            || tmux -2 -L netbook"
    else
 ssh $extra -t "$dest" "tmux -2 -L netbook attach \
            || tmux -2 -L netbook"
    fi
    
    stty sane
    echo "Dropped, press Enter to reconnect."
    if read x; then
 echo "Reconnecting..."
    else
 # Something bad happened to our tty. We'd better exit.
 exit 1
    fi
done

So now I can connect to work-host1 from home via the gateway, shut the laptop, travel to work, open it again, hit Enter and reconnect directly to work-host1 without losing any state. What's not to like? Well, there's still some more things we can do.

Sunday, 11 November 2012

ssh-and-tmux: part one

So, I finally got round to buying myself a netbook. Unlike my old laptop the netbook has a battery that actually works so I can pick it up, use it for a few minutes and then close the lid so it suspends. Later on I can pick it up again and carry on where I left off. At least that's the theory. The problem is that quite a lot of what I do ends up involving ssh connections to remote hosts and whilst those connections survive a brief period of suspension they don't last for too long.

I've used Screen on and off for over fifteen years and I thought that this would help to solve the problem. Not long after implementing the solution I switched to tmux.

Step one is to initiate tmux over the ssh connection:

ssh -t destination "tmux -2 -L netbook attach || tmux -2 -L netbook"

The -2 forces tmux to assume that the terminal supports 256 colours. All of the ones I use seem to.

The -L netbook option gives the tmux session a unique name so it won't get mixed up with a manually launched tmux session.

First I try and attach to an existing session but if that fails I create a new one.

I put this in a script named ssh-and-tmux.

Step two is to automatically reconnect when the connection is dropped. I decided not to do this automatically because that would keep throwing off other connections from elsewhere. Leaving multiple connections active at the same time would mean that the usable window size might be limited too. Instead I simply wait for the Enter key to be pressed before trying to connect again and if anything goes wrong exit.

So, the step two script is:

#!/bin/sh
if [ -n "$TMUX" ]; then
    echo Already in tmux
    exit 1
fi

if [ -n "$STY" ]; then
    echo Already in screen
    exit 1
fi

dest="$1"
while true; do
    ssh -t "$dest" "tmux -2 -L netbook attach || tmux -2 -L netbook"

    stty sane
    echo "Dropped, press Enter to reconnect."
    if read x; then
        echo "Reconnecting..."
    else
        # Something bad happened to our tty. We'd better exit.
        exit 1
    fi
done

Sometimes it can take ssh a while to notice that the connection has been dropped. In that case I can simply type " ~ . " to kill the ssh connection and reconnect.

This works well but there's more to come in the next post.

Wednesday, 17 February 2010

Updated Perforce scripts: show-non-p4

It was recently brought to my attention that the links to my Perforce scripts at www.fysh.org/~mac/perforce/ weren't working. These should now be fixed. As part of fixing them I noticed that some of the scripts were a little old and a slightly newer and (IMO) useful script wasn't linked at all.

Over two years ago I complained that it is hard to find files that need adding. Not long after I posted that I wrote a simple script called show-non-p4 to solve this. The script walks the directory tree looking for files that aren't in Perforce and don't match the patterns specified in per-directory .p4ignore files. The script also has a handful of global patterns that it always ignores but these are deliberately conservative.

I've found that when using sufficiently new versions of the P4API I need to lose the call to p4.tagged that occurs in the Ruby scripts. The versions available on that page still contain the line as of this writing though.

Wednesday, 14 October 2009

Running unit tests under gdb: Part II

In part one I described a script which could be used to easily run unit tests under gdb. In describing the script I wrote:

It's not ideal — it corrupts quoting of its arguments and arbitrarily limits the number of arguments but it does work.

I am indebted to a loyal reader for pointing out the rather obvious --args gdb parameter. I'm not really sure how I'd missed it. This makes fixing the script to remove those flaws trivial. If you're running only one test that you know is going to fail then it may be sufficient to just use RUN_TEST="gdb --args". However, if the problem is intermittent or you're running a whole gaggle of tests then the fixed script is still useful.

This version of the script also ensures that the temporary commands file is cleaned up too.

#!/bin/sh
cmds=`tempfile -p rug`
: > $cmds
echo 'run' >> $cmds
echo 'if $_exitcode == 0' >> $cmds
echo ' quit' >> $cmds
echo 'end' >> $cmds
trap 'if [ -n "$cmds" ]; then rm -f "$cmds" ;exit 1; fi' SIGINT SIGHUP SIGTERM ERR

if gdb --return-child-result --quiet -x $cmds --args "$@"; then
rm -f $cmds
else
result=$?
rm -f $cmds
exit $result
fi

Tuesday, 13 October 2009

Running unit tests under gdb: Part I

Our build system usually runs all the unit tests under valgrind. It does this by preceding the invocation of the compiled unit test with $(RUN_TEST) as in this simplified version:
RUN_TEST?=valgrind
my_unittest.pass : my_unittest
$(RUN_TEST) my_unittest
touch $@
This means that it's easy to run the test without valgrind by invoking
make RUN_TEST= my_unittest.pass
This can be useful when you know the test is going to fail and you don't want the huge amount of output that valgrind will spew when you abort() from the depths.

Sometimes it would be handy to run the unit tests under gdb but unfortunately invoking a program under gdb is not the same as invoking it normally so RUN_TEST=gdb doesn't work. Compare:
valgrind myprog myargs...
with:
gdb myprog
run myargs...
So, what's needed is a gdb wrapper script that turns the former method of invocation into the latter. It turns out that it's possible to do even better than that. The following script runs the program under gdb and if it succeeds just exits normally so that the next test can be tried. If the program breaks due to an abort then the if statement unfortunately produces an error message but it does have the intended effect — you're left at the gdb prompt.

Here's my run-under-gdb script:
#!/bin/sh
cmds=`tempfile -p rug`
prog=$1
shift
: > $cmds
echo "run $1 $2 $3 $4 $5 $6 $7 $8 $9" >> $cmds
echo 'if $_exitcode == 0' >> $cmds
echo ' quit' >> $cmds
echo 'end' >> $cmds
if gdb -x $cmds $prog; then
rm -f $cmds
else
result=$?
rm -f $cmds
exit $result
fi

It's not ideal — it corrupts quoting of its arguments and arbitrarily limits the number of arguments but it does work. Part two contains an improved script that solves these problems and provides other fixes.

Monday, 5 October 2009

A better way to view man pages in a different window

In Episode 13 of Season 2 of the Ubuntu UK Podcast the “Command Line Lurve” segment was supplied by Alan Bell. He found that reading man pages in a terminal to be painful because the terminal was where he wanted to being typing the command options. His script ran gnome-help in the background to view the man page in a different window.

Alan Bell's script prompted me to write an improved version and send it in along with this email:

I listened to Alan Bell's command line luurve segment in your podcast
this evening. Whilst his script works it inhibits much of man(1)'s
functionality. In particular it does not support specifying the manual
section (compare "man open" and "man 2 open" for example.)

Here's my alternative that maintains this functionality and
automatically falls back to standard man if any options are supplied:

#!/bin/sh
x="$DISPLAY"
case "$1" in
"") x= ;;
-*) x= ;;
esac
if [ -n "$x" ]; then
section=
for i in "$@"; do
case "$i" in
[0-9]) section="($i)" ;;
*) gnome-help "man:$i$section" >/dev/null 2>&1 & ;;
esac
done
else
exec man "$@"
fi

The script also makes specifying multiple pages at once more useful
than it is with man(1).
It can be aliased to man if required as Alan described.



They were nice enough to read out my email in Episode 14 but the script didn't appear in the show notes. So here it is.