23

Currently I use Fish as my main shell on local and remote hosts.

I connect to remote hosts via ssh and sftp. I wanted to open or reuse a remote tmux whenever I connect, automatically, by default; so I added this to my ~/.ssh/config:

Host example.com
RemoteCommand tmux a; or tmux
RequestTTY yes

The problem is that now I cannot connect through sftp, nor can I run a direct command from my local CLI:

➤ ssh example.com ping localhost
Cannot execute command-line and remote command.

➤ sftp example.com
Cannot execute command-line and remote command.
Connection closed

So, my question is: How can I define a default command to be executed when opening a new interactive SSH session, but make it overridable?

Yajo
  • 413

5 Answers5

15

Option 1 )

You can the option Match ( see man ssh_config )

Match Host example.com exec "test $_ = /usr/bin/ssh"
     RemoteCommand tmux a; or tmux
     RequestTTY yes

This will only differentiate difference between ssh & sftp

Option 2

You create some placeholder config for your diffe command , example :

Host tmux.example.com
  HostName example.com
  HostKeyAlias example.com
  RemoteCommand tmux a; or tmux
  RequestTTY yes

And after you can still use example.com for you sftp / ping usage .

rymo
  • 103
EchoMike444
  • 3,165
  • 1st option didn't work. It seems Fish doesn't export $_. However, I can manually export a variable with set -x auto_tmux 1 and change the match test to "[ 1 -eq $auto_tmux ]" – Yajo Jun 25 '18 at 09:04
  • 8
    Thanks, this helped me find an answer that worked for me: change the match to Match exec "[[ $(ps h o args p $PPID | wc -w) -eq 2 ]]". That exec is true when ssh is invoked as "ssh " but false if invoked as, say, "ssh " – cryptarch Nov 14 '18 at 06:56
  • if you have multiple hostnames nomrally space-separated like Host with.example.com multiple.example.com and you wish to use Match this way, try curly braces: Match Host {with.example.com,multiple.example.com} exec "..." – rymo Dec 03 '19 at 23:08
  • @rymo, I wasn't able to get that working with OpenSSH 8.0p1, dated 2019-05-28. Wasn't able to find any way to match on multiple hostnames with that version, except by adding it to the exec portion: Match exec "[[ %n == @(pihole|router|athena|nas) && $(ps h o args p $PPID | wc -w) -eq 2 ]]". – Head Geek Jan 03 '20 at 16:36
  • @cryptarch, I really like your answer, but I couldn't find a way to apply it to a Windows client. Any thoughts? – Autumn Apr 30 '23 at 01:43
  • @Autumn You'll probably want to check the manual for ssh_config. In particular, the exec runs everything in the quotation marks in your shell. Therefore, you will want to carefully inspect the command and check that it works as expected in your shell. I never touch Windows, so beyond this you're on your own :) – cryptarch May 01 '23 at 21:56
5

I'm using something more complex that deals with ssh options and space in arguments:

Match exec "POSIXLY_CORRECT=1 xargs -0 getopt -o 46AaCfGgKkMNnqsTtVvXxYyB:b:c:D:E:e:F:I:i:J:L:l:m:O:o:p:Q:R:S:W:w: --  </proc/$PPID/cmdline | perl -pe 's|.*? -- ||' | { read -r c; eval a=($c); [ ${#a[@]} -le 2 ]; }"
    RemoteCommand my-command-here
    RequestTTY yes

Also, if someone wants to use RemoteCommand for some machines (like inside "host xxxx"), the same trick can be used in reverse logic (match are evaluated after host):

Match exec "POSIXLY_CORRECT=1 xargs -0 getopt -o 46AaCfGgKkMNnqsTtVvXxYyB:b:c:D:E:e:F:I:i:J:L:l:m:O:o:p:Q:R:S:W:w: -- </proc/$PPID/cmdline | perl -pe 's|.*? -- ||' | { read -r c; eval a=($c); [ ${#a[@]} -gt 2 ]; }"
     RemoteCommand none
     RequestTTY auto

Host machine1 machine2 ...
     RemoteCommand my-command-here
     RequestTTY yes

If someone is curious, I'm currently using RemoteCommand to call screen:

RemoteCommand which screen &>/dev/null && TERM=xterm screen -q -RR "%u-%C" $SHELL -l || $SHELL -l
  • 1
    What's that supposed to do? – Yajo Feb 10 '19 at 07:07
  • Parses ssh args in a space/quotes-safe manner, ignores any valid ssh opt (including opt value when required) and count if the remaining args are only two ("ssh" and "host"). It's similar to @cryptarch solution but also considering extra ssh opts. – Luiz Angelo Daros de Luca Feb 11 '19 at 10:51
  • 3
    Ssh could save us some pain if it simply consider command in cmdline to have higher precedence than -o RemoteCommend – Luiz Angelo Daros de Luca Feb 11 '19 at 10:59
  • This doesn't work with Fish, my main shell, as explained in the question :/ – Yajo Feb 11 '19 at 11:06
  • You can add "| tee /dev/stderr" before "perl" to check what it outputs. It might need a new kind of filter – Luiz Angelo Daros de Luca Feb 13 '19 at 18:42
  • It fails to process any ssh command with more than 1 parameter, like ssh -v. Also, it doesn't support sh. Mine supports sh, and uses hostname to check: Match exec "POSIXLY_CORRECT=1 xargs -0 getopt -o 46AaCfGgKkMNnqsTtVvXxYyB:b:c:D:E:e:F:I:i:J:L:l:m:O:o:p:Q:R:S:W:w: -- </proc/$PPID/cmdline | perl -pe 's|.*? -- ||' | tee /tmp/ssh_match | bash -c ' echo %h >> /tmp/ssh_match; read -r c; eval a=($c); for (( i=0; i < ${#a[@]}; i++ )); do [[ ${a[$i]} == '%h' ]] && break; done; (( i++ )); echo $i ${#a[@]} >> /tmp/ssh_match; [ $i -lt ${#a[@]} ]; m=$?; echo $m >> /tmp/ssh_match; exit $m '" – Misty Jun 03 '22 at 12:04
1

One option would be to make a symlink alias to the ssh command:

ln -s /usr/bin/ssh $HOME/bin/ssht

To make life easier, make sure $HOME/bin is in your $PATH.

In your ssh config:

Match Host example.com exec "test $_ = $HOME/bin/ssht"
     RemoteCommand tmux new-session -A -s mysession
     RequestTTY yes

Now, you can choose to use ssht example.com or ssh example.com for tmux and non-tmux sessions, respectively.

j24
  • 11
1

here is a solution that worked for me.

It works for bash and fish

fish doesn't support $_ as @Yajo said

Match exec "ps --pid %%self -o ppid --no-headers | xargs -r ps ww --pid | grep /usr/bin/ssh"
  RemoteCommand tmux new -A
  RequestTTY yes

I used tmux new -A because my distant server supports tmux 3.1 and the automatic attach if session exists.

I had to use %%self to escape the % and be able to use %self.

Here is how it works

You ask to select the current process with --pid %self, but you ask to display your parent pid with -o ppid, then you ask to get only the value, and discard the header with --no-headers

so now you have the pid of what is supposed to be the ssh/scp/sftp command.

now you need to know, what command ran it.

| xargs -r ps ww --pid will launch ps ww --pid YOUR_PARENT_PID

The -r state for avoiding xargs to launch ps if the previous command matched nothing.

I'm using xargs and not $(), because fish doesn't support $() but () this difference would cause my code to work either in bash or fish but not both, so I'm using xargs

then we have the ps ww --pid YOUR_PARENT_PID : ww will dump the full command line so here you can play with fgrep as I did or wc as @cryptarch did.

0

This doesn't answer directly the question. However it has become for me the fix to the root problem, so I think it deserves a post.

What I really wanted is to auto-enter tmux. Instead, I just started using byobu, which builds on top of tmux but is way more comfortable. For example, it supports this use case out of the box.

After installing byobu, just run:

byobu-enable

And you're done. Next interactive session will enter byobu automatically, no matter the shell. Non-interactive connections will work as usual.

Yajo
  • 413