Auto attach to gnu screen on remote host (ssh) only - gnu-screen

So I distribute my dotfiles on all my machines. I want to put something like the following in my .zshrc
# Autoload screen if we aren't in it.
if [[ $STY = '' ]] then screen -xR; fi
Which I got from What's in your .zshrc?
But I only want to attach to screen when I ssh to remote hosts... not if I'm on my local machine. Is there an environment variable I can test to see if this is an SSH session or localhost console?

Right now I'm going to test the $SSH_TTY variable which seems to be reliable from the few hosts I've tried:
if [[ $STY = '' && $SSH_TTY != '' ]] then screen -xR; fi

Related

Is it possible to use the "code" command in SSH'ed terminal to open VS Code on local machine with SSH extension?

Something I love about VS Code is that when I am using a terminal in WSL, I can run code file.txt, and it will open that file with VS Code on my local using the WSL remote extension.
Is it possible to do a similar thing with SSH? I.e., if I am SSH'ed into a remote host, is it possible to set things up so that running code file.txt will open VS Code on my local machine, connected via the remote SSH extension to open that file?
I found much better & simple answer thanks to this post.
Simply create new script file named code with below contents & put file under any folder from $PATH. (echo $PATH to see what folders you can use)
#! /usr/bin/env zsh
local max_retry=10
for i in {1..$max_retry}
do
local script=$(echo ~/.vscode-server/bin/*/bin/remote-cli/code(*oc[$i]N))
if [[ -z ${script} ]]
then
echo "VSCode remote script not found"
exit 1
fi
local socket=$(echo /run/user/$UID/vscode-ipc-*.sock(=oc[$i]N))
if [[ -z ${socket} ]]
then
echo "VSCode IPC socket not found"
exit 1
fi
export VSCODE_IPC_HOOK_CLI=${socket}
${script} $# > /dev/null 2>&1
if [ "$?" -eq "0" ]; then
exit 0
fi
done
echo "Failed to find valid VS Code window"
Bash version
#! /bin/bash
max_retry=10
for i in $(seq 1 $max_retry)
do
recent_folder=$(ls ~/.vscode-server/bin/ -t | head -n$i)
script=$(echo ~/.vscode-server/bin/$recent_folder/bin/remote-cli/code)
if [[ -z ${script} ]]
then
echo "VSCode remote script not found"
exit 1
fi
socket=$(ls /run/user/$UID/vscode-ipc-* -t | head -n$i)
if [[ -z ${socket} ]]
then
echo "VSCode IPC socket not found"
exit 1
fi
export VSCODE_IPC_HOOK_CLI=${socket}
${script} $#
if [ "$?" -eq "0" ]; then
exit 0
fi
done
echo "Failed to find valid VS Code window"
Update
Above script doesn't work with recent updates. I had to change first line to
local script=$(echo ~/.vscode-server/bin/*/bin/remote-cli/code(*oc[1]N))
Update2
Original script may fail if recently opened ssh window is closed, yet there is another SSHed window open. I have enhanced the script to enable retrying the command with recent N(default 10) windows.
You shouldn't have to do anything. VSCode automatically sets the path/PATH to the code in the path/PATH environment variable depending on your shell. See this response. You might be overwriting your path/PATH like I was. I was accidentally overwriting path in ~/.cshrc and PATH in ~/.bashrc and was running into the same issue. After fixing it, I can run code on the command line. which code returns the location of the command.
Until I spent time to figure it out, I was using the two methods mentioned below. Both of which worked for me in bash; you can modify it for your shell as you see fit. But really fix your path/PATH rather than using these methods.
Adding location of code to the PATH in ~/.bashrc
export PATH=${VSCODE_GIT_ASKPASS_NODE%/*}/bin:$PATH
OR
Setting alias to code in ~/.bashrc
alias code="${VSCODE_GIT_ASKPASS_NODE%/*}/bin/code"
More on path vs. PATH here and here
Yes, sort of.
From a VSCode terminal run the command
env | grep VSCODE_IPC_HOOK_CLI
then copy-and-paste that line that line with export into your ssh terminal.
After that, you should be able to run code from your ~/.vscode-server/bin/XXX/bin directory.
VSCode terminal
SSH terminal
Update:
You can to automate this with a .bashrc and .profile to place the IPC code into a temp file, and source that when you do your ssh login.
For example, this works for me...
Append this to ~/.bashrc
#
if [ "$VSCODE_IPC_HOOK_CLI" != "" ]; then
cat >$HOME/.vscode_env.sh <<EOF
#
if [ "\$VSCODE_IPC_HOOK_CLI" = "" ]; then
export VSCODE_IPC_HOOK_CLI="$VSCODE_IPC_HOOK_CLI"
alias code="${VSCODE_GIT_ASKPASS_NODE%/*}/bin/code"
fi
EOF
fi
And append this to your ~/.profile
[ -f $HOME/.vscode_env.sh ] && . $HOME/.vscode_env.sh
(There may be more elegant ways. And you still have to start at least 1 terminal in your remote VSCode session.)
this works to me
if [[ -n "$SSH_CLIENT" || -n "$SSH_TTY" ]]; then
local script=$(echo ~/.vscode-server/bin/*/bin/remote-cli/code(*oc[1]N))
if [[ -z ${script} ]]
then
echo "VSCode remote script not found"
exit 1
fi
local socket=$(echo /run/user/$UID/vscode-ipc-*.sock(=oc[1]N))
if [[ -z ${socket} ]]
then
echo "VSCode IPC socket not found"
exit 1
fi
export VSCODE_IPC_HOOK_CLI=${socket}
alias code=${script}
fi
Use the below commands to open a folder or a file on the remote terminal.
Note: vscode-server must be already installed on the remote host (It would be, if you have already connected to it). Also the absolute path has to be specified for the file or folder. Use -n to launch in new window,-r to reuse same window.
code --folder-uri <absolute-path>
code --file-uri <absolute-path-file-name>
Example:
code -r --folder-uri /home/myscripts/src
code -n --file-uri /home/myscripts/src/math/sample.py

Base16 colors break under tmux ssh

I'm using the same dot files for all my machines.
On my local machine everything works just fine in tmux, when sshing over to
one of my machines the colors breaks. Colors also work sshing in general outside of tmux.
General settings can be seen in the image
Since there is not tmux session running on the remote host (tmux running locally with ssh happening in one of the panes), there is no $TMUX environment variable set.
Base16 script expects there to be such a flag for RGBs to be properly set
if [ -n "$TMUX" ]; then
# Tell tmux to pass the escape sequences through
# (Source: http://permalink.gmane.org/gmane.comp.terminal-emulators.tmux.user/1324)
put_template() { printf '\033Ptmux;\033\033]4;%d;rgb:%s\033\033\\\033\\' $#; }
put_template_var() { printf '\033Ptmux;\033\033]%d;rgb:%s\033\033\\\033\\' $#; }
put_template_custom() { printf '\033Ptmux;\033\033]%s%s\033\033\\\033\\' $#; }
elif [ "${TERM%%[-.]*}" = "screen" ]; then
...
Workaround is currently to fix the if statement in each script.

Cygwin ssh not setting DISPLAY correctly (extra :0)

I am using Cygwin on Windows 7 to connect to a unix (RHEL 6) server via SSH. When I do so, X forwarding is not being set correctly:
(Starting from Cygwin xterm on the Windows machine)
> echo $DISPLAY
:0
> ssh -Y myname#rhel.server.com
[enter password]
> echo $DISPLAY
localhost:52.0:0
If I now try to start any windowed process, I get an error message:
> emacs
emacs: Cannot connect to X server localhost:52.0:0.
Check the DISPLAY environment variable or use '-d'.
Also use the 'xhost' program to verify that it is set to permit connections from your machine.
Additional Details
The number that follows 'localhost' (52 in the example above) is different each time I connect.
If I do the following,
> export DISPLAY=localhost:52.0
(that is, I remove the trailing ":0) then this DOES fix the issue, and the window forwarding works just fine.
So the question is: how to fix so that I do not need to manually change the DISPLAY variable with each connection?
A false alarm, much to my embarrassment. Turns out my .profile (that I had borrowed from someone else, as it contained a large quantity of useful customization for our development environment) contained these lines:
export DISPLAY
if [[ -z $DISPLAY ]]; then
DISPLAY=my.machine.addr:0
elif echo $DISPLAY | grep -z -v ':0'; then
DISPLAY=$DISPLAY:0
fi
Lo and behold, commenting out the elif branch solves the problem. May this be a lesson to us all on the dangers of copying someone else's config without knowing what it contains.

How to automatically start tmux on SSH session?

I have ten or so servers that I connect to with SSH on a regular basis. Each has an entry in my local computer's ~/.ssh/config file.
To avoid losing control of my running process when my Internet connection inevitably drops, I always work inside a tmux session. I would like a way to have tmux automatically connect every time an SSH connection is started, so I don't have to always type tmux attach || tmux new after I SSH in.
Unfortunately this isn't turning out to be as simple as I originally hoped.
I don't want to add any commands to the ~/.bashrc on the servers because I only want it for SSH sessions, not local sessions.
Adding tmux attach || tmux new to the ~/.ssh/rc on the servers simply results in the error not a terminal being thrown after connection, even when the RequestTTY force option is added to the line for that server in my local SSH config file.
Server-side configuration:
To automatically start tmux on your remote server when ordinarily logging in via SSH (and only SSH), edit the ~/.bashrc of your user or root (or both) on the remote server accordingly:
if [[ $- =~ i ]] && [[ -z "$TMUX" ]] && [[ -n "$SSH_TTY" ]]; then
tmux attach-session -t ssh_tmux || tmux new-session -s ssh_tmux
fi
This command creates a tmux session called ssh_tmux if none exists, or reattaches to a already existing session with that name. In case your connection dropped or when you forgot a session weeks ago, every SSH login automatically brings you back to the tmux-ssh session you left behind.
Connect from your client:
Nothing special, just ssh user#hostname.
Don't do this on the server-side!
That is potentially dangerous because you can end up being locked-out of the remote machine. And no shell hacks / aliases / etc. are required, either.
Instead...
... make use of (your client's) ~/.ssh/config like so:
tmux 3.1 or newer¹ on the remote machine
Into your local ~/.ssh/config, put²:
Host myhost
Hostname host
User user
RequestTTY yes
RemoteCommand tmux new -A -s foobar
As pointed out by #thiagowfx, this has the side effect of making it impossible to use, e.g. ssh myhost ls /tmp and should therefore not be used with Host * ... what I like to do is to have a Host myhost section with RemoteCommand tmux ... and then in addition to that I'll have a Host MYHOST section without it.
Instead of RequestTTY yes you could call ssh with the -t switch; thank you, #kyb.
Off-topic, but if you're dealing with non-ASCII characters, I'd recommend to change that into tmux -u … for explicitly enabling Unicode support even on machines that don't have the proper environment variables set.
tmux 3.0a or older on the remote machine
Almost the same as above, but change the last line to³:
RemoteCommand tmux at -t foobar || tmux new -s foobar
¹ repology.org has a list of distros and their tmux versions
² new is short for new-session.
³ at is short for attach-session.
Only if, for some reason, you really, really can't do it client-side:
Using the remote's authorized_keys file
If you would rather not have an ~/.ssh/config file for whatever reason, or want the remote machine to force the connecting machine to connect to / open the session, add this to your remote ~/.ssh/authorized_keys:
command="tmux at -t foobar || tmux new -s foobar" pubkey user#client
This will, of course, work from all clients having the corresponding private key installed, which some might consider an upside –– but: should anything go wrong, it might not be possible to connect anymore without (semi-)physical access to the machine!
One caveat!
As #thiagowfx notes in the comments, this should not be put underneath Host * as it breaks certain things, such as git push. What I personally do is to add a second entry in all-uppercase letters for where I want to automatically be connected to tmux.
Alright, I found a mostly satisfactory solution. In my local ~/.bashrc, I wrote a function:
function ssh () {/usr/bin/ssh -t "$#" "tmux attach || tmux new";}
which basically overwrites the ssh terminal function to call the built-in ssh program with the given arguments, followed by "tmux attach || tmux new".
(The $# denotes all arguments provided on the command line, so ssh -p 123 user#hostname will be expanded to ssh -t -p 123 user#hostname "tmux attach || tmux new")
(The -t argument is equivalent to RequestTTY Force and is necessary for the tmux command.)
Connect:
ssh user#host -t "tmux new-session -s user || tmux attach-session -t user"
During session:
Use Ctrl+d to finish session (tmux window closes) or Ctrl+b d to temporary detach from session and connect to it again later.
Remember! If your server restarted session lost!
When you are inside tmux anytime you can use Ctrl+b s to see sessions list and switch current to another.
Fix your .bashrc:
I recommend you to define universal function in your .bashrc:
function tmux-connect {
TERM=xterm-256color ssh -p ${3:-22} $1#$2 -t "tmux new-session -s $1 || tmux attach-session -t $1"
}
It uses 22 port by default. Define your fast-connect aliases too:
alias office-server='tmux-connect $USER 192.168.1.123'
alias cloud-server='tmux-connect root my.remote.vps.server.com 49281'
Login without password:
And if you don't want to type password everytime than generate .ssh keys to login automatically:
ssh-keygen -t rsa
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_rsa
Put your public key to the remote host:
ssh-copy-id -p <port> user#hostname
Additional tips:
If you want to use temporary session-id which corresponds with a local bash session use as tmux id:
SID=$USER-$BASHPID
ssh user#host -t "tmux new-session -s $SID || tmux attach-session -t $SID"
I used lines from #kingmeffisto (I'm not allowed to comment that answer) and I added an exit so terminating tmux also terminates the ssh connection. This however broke SFTP sessions so I had to check for $SSH_TTY instead of $SSH_CONNECTION.
EDIT 4/2018: Added test for interactive terminal via [[ $- =~ i ]] to allow tools like Ansible to work.
if [ -z "$TMUX" ] && [ -n "$SSH_TTY" ] && [[ $- =~ i ]]; then
tmux attach-session -t ssh || tmux new-session -s ssh
exit
fi
As described in this blog post you can ssh and then attach to an existing tmux session with a single command:
ssh hostname -t tmux attach -t 0
This is the one that actually creates a great user-experience.
It automatically starts tmux whenever you open the terminal (both physically and ssh).
You can start your work on one device, exit the terminal, and resume on the other one. If it detects someone already attached to the session it will create new session.
Put it on the server, depending on your shell ~/.zshrc or ~/.bashrc.
if [[ -z "$TMUX" ]] ;then
ID="$( tmux ls | grep -vm1 attached | cut -d: -f1 )" # get the id of a deattached session
if [[ -z "$ID" ]] ;then # if not available attach to a new one
tmux new-session
else
tmux attach-session -t "$ID" # if available attach to it
fi
fi
I have the following solution that gives you two SSH hosts to connect to: one with tmux, one without:
# Common rule that 1) copies your tmux.conf 2) runs tmux on the remote host
Host *-tmux
LocalCommand scp %d/.tmux.conf %r#%n:/home/%r/
RemoteCommand tmux new -As %r
RequestTTY yes
PermitLocalCommand yes
# Just connect.
# Notice the asterisk: makes possible to re-use connection parameters
Host example.com*
HostName example.com
User login
# Connect with tmux
Host example.com-tmux
HostKeyAlias dev.dignio.com
You might find this useful - uses ssh in a loop and reconnects to or connects to an existing tmux session so you have a nice easy reliable
way to reconnect after a network outage
#!/bin/bash
#
# reconnect to or spawn a new tmux session on the remote host via ssh.
# If the network connection is lost, ssh will reconnect after a small
# delay.
#
SSH_HOSTNAME=$1
TMUX_NAME=$2
PORT=$3
if [[ "$PORT" != "" ]]
then
PORT="-p $PORT"
fi
if [ "$TMUX_NAME" = "" ]
then
SSH_UNIQUE_ID_FILE="/tmp/.ssh-UNIQUE_ID.$LOGNAME"
if [ -f $SSH_UNIQUE_ID_FILE ]
then
TMUX_NAME=`cat $SSH_UNIQUE_ID_FILE`
TMUX_NAME=`expr $TMUX_NAME + $RANDOM % 100`
else
TMUX_NAME=`expr $RANDOM % 1024`
fi
echo $TMUX_NAME > $SSH_UNIQUE_ID_FILE
TMUX_NAME="id$TMUX_NAME"
fi
echo Connecting to tmux $TMUX_NAME on hostname $SSH_HOSTNAME
SLEEP=0
while true; do
ssh $PORT -o TCPKeepAlive=no -o ServerAliveInterval=15 -Y -X -C -t -o BatchMode=yes $SSH_HOSTNAME "tmux attach-session -t $TMUX_NAME || tmux -2 -u new-session -s $TMUX_NAME"
SLEEP=10
if [ $SLEEP -gt 0 ]
then
echo Reconnecting to session $TMUX_NAME on hostname $SSH_HOSTNAME in $SLEEP seconds
sleep $SLEEP
fi
done
byobu is a nice useful wrapper for tmux/screen. Connects to an existing session if present or creates a new one.
I use it with autossh which gracefully reconnects the ssh session. Highly recommended in case of intermittent connectivity issues.
function ssh-tmux(){
if ! command -v autossh &> /dev/null; then echo "Install autossh"; fi
autossh -M 0 $* -t 'byobu || {echo "Install byobu-tmux on server..."} && bash'
}
I know I'm reviving an old thread but I've done some work on the bashrc solution and I think it has some use:
#attach to the next available tmux session that's not currently occupied
if [[ -z "$TMUX" ]] && [ "SSH_CONNECTION" != "" ];
then
for i in `seq 0 10`; do #max of 10 sessions - don't want an infinite loop until we know this works
SESH=`tmux list-clients -t "$USER-$i-tmux" 2>/dev/null` #send errors to /dev/null - if the session doesn't exist it will throw an error, but we don't care
if [ -z "$SESH" ] #if there's no clients currently connected to this session
then
tmux attach-session -t "$USER-$i-tmux" || tmux new-session -s "$USER-$i-tmux" #attach to it
break #found one and using it, don't keep looping (this will actually run after tmux exits AFAICT)
fi #otherwise, increment session counter and keep going
done
fi
There's a cap at 10 (11) sessions for now - I didn't want to kill my server with an infinite loop in bashrc. It seems to work pretty reliably, other than the error of tmux failing on list-clients if the session doesn't exist.
Thie way allows you to reconnect to an old tmux instance if your ssh session drops. The exec saves a fork of course.
if [ -z "$TMUX" ]; then
pid=$(tmux ls | grep -vm1 "(attached)" | cut -d: -f1)
if [ -z "$pid" ]; then
tmux new -d -s $pid
fi
exec tmux attach -t $pid
fi
Append to bottom of your remote server's ~/.bashrc, (or possibly its /etc/.bashrc.shared (1)):
# ======================== PUT THIS LAST IN .BASHRC ==========================
# --- If we're run by SSH, then auto start `tmux` and possibly re-attach user.
# $- interactive only via current option flags
# -z $TMUX no tmux nesting
# $SSH_TTY SSH must be running, and in a shell
#
if [[ $- == *i* ]] && [[ -z "$TMUX" ]] && [[ -n "$SSH_TTY" ]]; then
tmux attach-session -t "$USER" || tmux new-session -s "$USER" && exit
fi
Many good tips above combined here, e.g. $- and $SSH_TTY are better I think.
And I like adding a few comments to help this old guy remember what's going on without having to look it up.
And finally, I like an exit at the end to cleanly come home when I'm done.
Thanks everyone.
Note I source a shared /etc/.bashrc.shared at the end of both user and root's .bashrc's, for common stuff used in both, like colorized ls, various aliases, functions and path extensions, i.e. I don't want redundant code in my root/.bashrc nor user/.bashrc.
This guys script works great. Just copy the bashrc-tmux file to ~/.bashrc-tmux and source it from ~/.bashrc right after the PS1 check section.

Automatically (or more easily) reconnect to a screen session after network interruption

ADDED: This question is now, I believe, subsumed by this one:
Using GNU Screen completely transparently and automatically
See also this related question:
https://superuser.com/questions/147873/ssh-sessions-in-xterms-freeze-for-many-minutes-whenever-they-disconnect
Original question:
It would be nice if there were a way to ssh to a machine and immediately reconnect to a specific screen session. You can do this:
laptop> ssh server.com screen -ls
and it will show a list of screens available on server.com like so [1]:
123.pts-1
456.pts-2
And then you might try to do this:
laptop> ssh server.com screen -dr pts-2
but that fails, saying "Must be connected to a terminal."
You have to ssh in first and then do the "screen -dr pts-2" on server.com which is no good if you have a flaky connection and get disconnected a lot. You want to be able to resume with a simple "up-arrow enter" on the laptop. (Or perhaps make it even more automatic.)
I have a rihackulous solution to this problem which I'll post as an answer and hope it gets downvoted to oblivion in favor of the Right Way to deal with this.
Footnotes:
[1] Or, better, if you created the screen sessions with names like "screen -S foo" and "screen -S bar" then you'll get a friendlier list like:
123.foo
456.bar
and can reconnect with, eg, "screen -dr foo".
Mini screen tutorial, incorporating the answer to this question:
Login in to server.com and do
screen -S foo
and then never log out of that session again.
To reconnect to it from elsewhere, do
ssh -t server.com screen -dr foo
To list available screens to reconect to:
screen -ls
or, of course,
ssh server.com screen -ls
to check on server.com's available screens remotely.
I now use the following alias (tcsh), based on Jason's answer below, to connect to a named screen if it exists or create and connect otherwise:
alias ssc 'ssh -t \!:1 "screen -S \!:2 -dr || screen -S \!:2"'
Does the -t option do what you want?
-t Force pseudo-tty allocation. This can be used to execute arbi-
trary screen-based programs on a remote machine, which can be
very useful, e.g. when implementing menu services. Multiple -t
options force tty allocation, even if ssh has no local tty.
So:
laptop> ssh -t server.com screen -dr pts-2
This seems to work in my installation.
This is now subsumed by this: Using GNU Screen completely transparently and automatically
Here's a script, ssc, that works just like ssh but takes a third argument to specify the screen to reconnect to, or the name of a new screen.
I believe this script subsumes everything in the original question.
#!/usr/bin/env perl
# Use 'ssc' (this script) instead of 'ssh' to log into a remote machine.
# Without a 3rd argument it will list available screens.
# Give it a 3rd argument to attach to an existing screen or specify a new
# screen. Eg, ssc remote.com foo
# The numbers in front of the screen tag can usually be ignored.
# Screen is a little too clever though in that if there's an existing screen "bar"
# and you say "ssc remote.com b" it will reconnect you to "bar" instead of making
# a new screen "b". It's like invisible and silent tab-completion.
if(scalar(#ARGV)==0 || scalar(#ARGV) > 2) {
print "USAGE: ssc remote.com [screen name]\n";
} elsif (scalar(#ARGV) == 1) {
$machine = shift;
#screens = split("\n", `ssh $machine screen -ls`);
for(#screens) {
if(/^\s*(\d+)\.(\S+)\s+\(([^\)]*)\)/) {
($num, $tag, $status) = ($1, $2, $3);
if($status =~ /attached/i) { $att{"$num.$tag"} = 1; }
elsif($status =~ /detached/i) { $att{"$num.$tag"} = 0; }
else { print "Couldn't parse this: $_\n"; }
}
}
print "ATTACHED screens:\n";
for(keys(%att)) { print " $_\n" if $att{$_}; }
print "DETACHED screens:\n";
for(keys(%att)) { print " $_\n" unless $att{$_}; }
} else {
$machine = shift;
$tag = shift;
system("ssh -t $machine \"screen -S $tag -dr || screen -S $tag\"");
}
Use the -t option to ssh to allocate a terminal while directly running a command.
laptop> ssh -t server.com screen -dr pts-2
I've been working on something similar but not quite got there, your solutions have solved my problem so here's my suggestion:
ssh -t server.com "screen -S foo -rd || screen -S foo"
This just tries to open the existing screen named foo and if it doesnt exist, creates it.
I'll put this in a launcher on my laptop, so when the wireless network goes I can just open where I left off.
Just noticed that the default screen shell is a bit weak, so an improvement which sets up your home environment a little better is:
ssh -t server.com "screen -S foo -rd || screen -S foo bash -l"
I converted this to work on OS X .bash_profile with one addition: If no 2nd parameter is given, it will start a session "default".
function ssc() {
if [[ -z $2 ]]; then
screen="default"
else
screen=$2
fi
ssh -t $1 "screen -S $screen -dr || screen -S $screen"
}
If you like to connect to the same session always even it is active, detached or not exists yet:
ssh -t user#server screen -xR screenName
The same but create a new session if it is already active on some other pty:
ssh -t user#server screen -rR screenName