More detailed SSH docs
https://github.com/romkatv/zsh4humans#SSH mentions that the environment can be sent with you over SSH but I can't seem to find details on how to make that happen.
After grepping my way through ~/.cache/zsh4humans I found z4h help ssh, so I gave it a try on a random GCP host with
z4h ssh -i ~/.ssh/id_rsa -- matschaffer@(IP)
But on the remote host I'm still just getting a typical ubuntu bash prompt.
Sorry if I'm missing something and thanks in advance for the advice!
SSH teleportation works through regular ssh commands. Plain ssh -i ~/.ssh/id_rsa -- matschaffer@(IP) will teleport your shell environment if teleportation is enabled for (IP). It's disabled by default for all hosts though. Here's the relevant part of .zshrc:
https://github.com/romkatv/zsh4humans/blob/0987e32ad9a67906aadf068c440c9b3ff69b7bab/.zshrc#L31-L40
You can try the whitelist approach first: keep the fallback value of enable at no and list specific hosts that should have teleportation enabled. Note that the hostname part of zstyle context is exactly what you pass in ssh hostname command. In your case it's (IP).
I usually add all hosts to ~/.ssh/config so that I can type ssh blah instead of the longer command. This requires the following block in ~/.ssh/config or a file included from it:
Host blah
HostName (IP)
User matschaffer
IdentityFile ~/.ssh/id_rsa
If you do it this way, I recommend adding the following styles to ~/.zshrc:
zstyle ':completion:*:ssh:argument-1:' tag-order hosts users
zstyle ':completion:*:scp:argument-rest:' tag-order hosts files users
zstyle ':completion:*:(ssh|scp|rdp):*:hosts' hosts
This will allow you to TAB-complete hosts nicely.
If you like ssh teleportation, you can switch to the blacklist approach (I do it this way): flip the fallback to yes and list hosts that should NOT have ssh teleportation. You can force-disable teleportation by using command ssh instead of ssh.
Ahhh.. that worked a treat! And thanks for the ssh config tip.
~ ······························································· 16:03:48
❯ ssh -F ~/.ssh/vscode gcp-workstation
z4h: fetching z4h.zsh
z4h: installing zsh4humans
z4h: installing systemd completions
z4h: installing zsh-history-substring-search
z4h: installing zsh-autosuggestions
z4h: installing zsh-completions
z4h: installing zsh-syntax-highlighting
z4h: installing terminfo
z4h: installing fzf
z4h: fetching fzf binary
z4h: installing tmux
z4h: installing ohmyzsh/ohmyzsh
z4h: installing powerlevel10k
z4h: fetching gitstatus binary
z4h: initializing zsh
direnv: loading ~/.envrc
direnv: export +GITHUB_TOKEN
~ ··································································································· ▼ matschaffer@workstation-matschaffer 07:03:56
I may add this host to my main ssh config, but for now I've been keeping it in a dedicated place for my vscode ssh remote hosts.
I've opened https://github.com/romkatv/zsh4humans/pull/151 with a recommendation that I think might have pointed me in the right direction. Though learning how z4h cloned itself was very interesting too :)
The discoverability of z4h features is currently awful. This is partially caused by my laziness and partially intentional. The intentional part is that I want the barrier to entry to be artificially high at the moment to avoid opening the floodgates. I spend enough of my free time providing user support for powerlevel10k and I don't want to double that. Eventually I plan to polish z4h and write docs but not soon.
By the way, you can make command history on gcp-workstation "persistent". That is, you ssh to it, run some commands, disconnect, completely wipe the instance, then ssh again (perhaps to a different IP this time) and have your command history in there as if nothing has happened. If you are interested in this, I can show you how to configure it. It'll take a few minutes of my time, so I'm first asking if it'll be useful to you.
Wow, so it's server history but persisted client side? Sounds pretty awesome and I'd love to hear more. Curious if it extends to things like sessions after sudo -i as well.
You're a magician @romkatv - don't let anyone tell you otherwise! 🧡
Wow, so it's server history but persisted client side?
That's right. More accurately, history gets pulled from remote hosts to the local, and when you open an ssh connection it gets sent from local to remote. It's as "persistent" as your local history. If you wipe your local machine, all history is gone unless you backup your history together with dotfiles like I do.
I'd love to hear more.
On second thought I think we can do it differently. Instead of my explaining how you can configure distributed command history on top of the existing low-level API, I'll add a new higher-level API that you can test drive. This way you'll get a simpler and more powerful setup and I'll get feedback that I need to ensure that the feature is useful and understandable. This will take some time. I'll ping you here when it's ready. How does that sound?
Curious if it extends to things like sessions after
sudo -ias well.
It does not. Try sudo -Es instead.
I'll add a new higher-level API that you can test drive. This way you'll get a simpler and more powerful setup and I'll get feedback that I need to ensure that the feature is useful and understandable. This will take some time. I'll ping you here when it's ready. How does that sound?
Sounds great! I'll keep an eye out for the notification.
It does not. Try sudo -Es instead.
Gotcha. I might even think of this as a "feature". :)
Instead of my explaining how you can configure distributed command history on top of the existing low-level API, I'll add a new higher-level API that you can test drive.
This will take a lot longer than I've originally expected due to life intervening and all that. I'll keep the issue open.
Thanks for the update @romkatv , no worries.
I was chatting with @aaronfeng about this yesterday and he was curious if it'd be possible to have shared history for a collection of ssh targets.
For example when working with EC2, hosts come and go all the time, but it'd be great to preserve the history.
I suspect if you had a ProxyCommand and accessed all hosts via a ssh config Host, this would work. But it might be a nice option to just have one common history file for a glob of SSH targets.
Looking forward to hearing more whenever your schedule allows. Take care of yourself first! 🧡
I was chatting with @aaronfeng about this yesterday and he was curious if it'd be possible to have shared history for a collection of ssh targets.
For example when working with EC2, hosts come and go all the time, but it'd be great to preserve the history.
Yep, that's exactly the use case that shared history in z4h was implemented for.
To not keep you waiting until I implemented a new API, here's something to get you started that works with the current (rather complex I'm afraid) API.
# This function is invoked by zsh4humans on every ssh command after
# the instructions from ssh-related zstyles have been applied. It allows
# us to configure ssh teleportation in ways that cannot be done with
# zstyles.
#
# Within this function we have readonly access to the following parameters:
#
# - z4h_ssh_client local hostname
# - z4h_ssh_host remote hostname as it was specified on the command line
#
# We also have read & write access to these:
#
# - z4h_ssh_enable 1 to use ssh teleportation, 0 for plain ssh
# - z4h_ssh_send_files list of files to send to the remote; keys are local
# file names, values are remote file names
# - z4h_ssh_retrieve_files the same as z4h_ssh_send_files but for pulling
# files from remote to local
# - z4h_retrieve_history list of local files into which remote $HISTFILE
# should be merged at the end of the connection
# - z4h_ssh_command command to use instead of `ssh`
function z4h-ssh-configure() {
emulate -L zsh
# Bail out if ssh teleportation is disabled. We could also
# override this parameter here if we wanted to.
(( z4h_ssh_enable )) || return 0
# Figure out what kind of machine we are about to connect to.
local machine_tag
case $z4h_ssh_host in
ec2-*) machine_tag=ec2;;
*) machine_tag=$z4h_ssh_host;;
esac
# This is where we are locally keeping command history
# retrieved from machines of this kind.
local local_hist=$ZDOTDIR/.zsh/history/retrieved_from_$machine_tag
# This is where our $local_hist ends up on the remote machine when
# we connect to it. Command history from files with names like this
# is explicitly loaded by our zshrc (see below). All new commands
# on the remote machine will still be written to the regular $HISTFILE.
local remote_hist='"$ZDOTDIR"/.zsh/history/received_from_'${(q)z4h_ssh_client}
# At the start of the SSH connection, send $local_hist over and
# store it as $remote_hist.
z4h_ssh_send_files[$local_hist]=$remote_hist
# At the end of the SSH connection, retrieve $HISTFILE from the
# remote machine and merge it with $local_hist.
z4h_retrieve_history+=($local_hist)
}
# Load command history that was sent to this machine over ssh.
() {
emulate -L zsh -o extended_glob
local hist
for hist in $ZDOTDIR/.zsh/history/received_from_*(NOm); do
fc -RI $hist
done
}
You'll need to add this block to ~/.zshrc below z4h init. Before trying it out you'll probably want to modify the logic that computes machine_tag based on $z4h_ssh_host, although you can also use it as is -- there is a reasonable fallback. Oh, and make sure to run z4h update beforehand -- this code relies on https://github.com/romkatv/zsh4humans/commit/5fea79d79748bbc8e222690ca44272213843611c that I have just pushed.
If you are defining z4h-ssh-configure, you don't actually need to use ssh-specific zstyles but you still can if you want to. The function is invoked after zstyles are applied, so you can observe and/or override their effect within z4h-ssh-configure. For example, z4h_ssh_enable within the function is set to 0 or 1 according to the value of zstyle :z4h:ssh:$hostname enable. The implementation of z4h-ssh-configure that I've posted above bails out if z4h_ssh_enable is zero, so it doesn't do anything unless you enable ssh teleportation via zstyle for the target host. You could instead set z4h_ssh_enable in the function itself based on $z4h_ssh_host or anything else.
You can add the following line at the top of z4h-ssh-configure to see the initial values of all ssh parameters that z4h lets you read/write.
typeset -pm 'z4h_ssh_*'
You'll notice that there are a few more parameters than what I've documented in the comments above z4h-ssh-configure. Those are low-level blocks of code that get executed on the remote host. You probably shouldn't touch them.
thanks @romkatv - I'll take it for a test drive and let you know if it works as expected. These past couple weeks aren't as SSH heavy for me so maybe I can rope in @aaronfeng too :)
Did you have a chance to try it out?
Not yet @romkatv - I've been doing a lot of selenium work lately which doesn't like to run on anything other than my laptop. Will send an update when I get a chance to use it.
There are barebone docs for SSH teleportation here: https://github.com/romkatv/zsh4humans/blob/master/tips.md#ssh