[ -z "$PS1" ] && return
sshb() {
scp ~/.bashrc ${1}:
ssh $1
}
# the rest of the .bashrc
alias c=cat
...
Esta é uma maneira muito ingênua com várias desvantagens óbvias:
- Você pode substituir um .bashrc existente
- Em vez de uma conexão, estabelecemos 2
- Como resultado, você também terá que fazer login 2 vezes.
- O argumento da função só pode ser o endereço da máquina remota
Opção melhorada:
[ -z "$PS1" ] && return
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
$ssh placeholder "cat >~/.bash-ssh" <~/.bashrc
$ssh "$@" -t "bash --rcfile ~/.bash-ssh -i"
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Agora usamos apenas uma conexão por meio de multiplexação. .bashrc é copiado para um arquivo que não é usado pelo bash por padrão e nós o especificamos explicitamente por meio da opção --rcfile. O argumento da função pode ser não apenas o endereço da máquina remota, mas também outras opções de ssh.
Em princípio, pode-se parar por aqui, mas a solução resultante tem uma desvantagem desagradável. Se você executar screen ou tmux, o .bashrc na máquina remota será usado e todos os seus apelidos e funções serão perdidos. Felizmente, isso pode ser superado. Para fazer isso, precisamos criar um script wrapper, que declararemos como nosso novo shell. Vamos supor para simplificar que já temos um script de wrapper na máquina remota e está localizado em ~ / bin / bash-ssh. O script é semelhante a este:
#!/bin/bash
exec /bin/bash --rcfile ~/.bash-ssh “$@”
E .bashrc assim:
[ -n "$SSH_TTY" ] && export SHELL="$HOME/bin/bash-ssh"
[ -z "$PS1" ] && return
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
$ssh placeholder "cat >~/.bash-ssh" <~/.bashrc
$ssh "$@" -t "bash --rcfile ~/.bash-ssh -i"
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Se a variável SSH_TTY existe, entendemos que estamos na máquina remota e substituímos a variável SHELL. De agora em diante, ao iniciar um novo shell interativo, um script será lançado que iniciará o bash com uma configuração não padrão salva quando uma sessão ssh foi estabelecida.
Para obter uma solução de trabalho conveniente, resta descobrir como criar um script de wrapper em uma máquina remota. Em princípio, você pode criá-lo na configuração do bash que salvamos desta forma:
[ -n "$SSH_TTY" ] && {
mkdir -p "$HOME/bin"
export SHELL="$HOME/bin/bash-ssh"
echo -e '#!/bin/bash\nexec /bin/bash --rcfile ~/.bash-ssh "$@"' >$SHELL
chmod +x $SHELL
}
Mas você pode realmente sobreviver com um único arquivo ~ / .bash-ssh:
#!/bin/bash
[ -n "$SSH_TTY" ] && [ "${BASH_SOURCE[0]}" == "${0}" ] && exec bash --rcfile "$SHELL" "$@"
[ -z "$PS1" ] && return
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
$ssh placeholder "cat >~/.bash-ssh" <~/.bashrc
$ssh "$@" -t 'SHELL=~/.bash-ssh; chmod +x $SHELL; bash --rcfile $SHELL -i'
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Agora, o arquivo ~ / .bash-ssh é um script independente e uma configuração bash. Funciona assim. Na máquina local, os comandos após [-n "$ SSH_TTY"] são ignorados. Na máquina remota, a função sshb cria um arquivo ~ / .bash-ssh e o usa como uma configuração para iniciar uma sessão interativa. A construção ["$ {BASH_SOURCE [0]}" == "$ {0}"] permite que você determine se um arquivo é carregado por outro script ou iniciado como um script autônomo. Como resultado, quando ~ / .bash-ssh é usado
- como config - exec é ignorado
- como um script - o controle passa para o bash e a execução de ~ / .bash-ssh termina com o exec.
Agora, ao se conectar via ssh, seu ambiente terá a mesma aparência em todos os lugares. É muito mais conveniente trabalhar desta forma, mas o histórico de execução do comando permanecerá nas máquinas às quais você se conectou. Pessoalmente, gostaria de salvar o histórico localmente para poder relembrar o que exatamente fiz em algumas máquinas no passado. Para fazer isso, precisamos dos seguintes componentes:
- Servidor TCP na máquina local que receberia dados de um soquete e os redirecionaria para um arquivo
- Encaminhe a porta de escuta deste servidor para a máquina com a qual nos conectamos via ssh
- PROMPT_COMMAND nas configurações do bash, que enviaria a atualização do histórico para a porta encaminhada após a conclusão do comando
Isso pode ser feito assim:
#!/bin/bash
[ -n "$SSH_TTY" ] && [ "${BASH_SOURCE[0]}" == "${0}" ] && exec bash --rcfile "$SHELL" "$@"
[ -z "$PS1" ] && return
[ -z "$SSH_TTY" ] && {
history_port=26574
netstat -lnt|grep -q ":${history_port}\b" || {
umask 077 && nc -kl 127.0.0.1 "$history_port" >>~/.bash_eternal_history &
}
}
HISTSIZE=$((1024 * 1024))
HISTFILESIZE=$HISTSIZE
HISTTIMEFORMAT='%t%F %T%t'
update_eternal_history() {
local histfile_size=$(stat -c %s $HISTFILE)
history -a
((histfile_size == $(stat -c %s $HISTFILE))) && return
local history_line="${USER}\t${HOSTNAME}\t${PWD}\t$(history 1)"
local history_sink=$(readlink ~/.bash-ssh.history 2>/dev/null)
[ -n "$history_sink" ] && echo -e "$history_line" >"$history_sink" 2>/dev/null && return
local old_umask=$(umask)
umask 077
echo -e "$history_line" >> ~/.bash_eternal_history
umask $old_umask
}
[[ "$PROMPT_COMMAND" == *update_eternal_history* ]] || export PROMPT_COMMAND="update_eternal_history;$PROMPT_COMMAND"
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
local bashrc=~/.bashrc
[ -r ~/.bash-ssh ] && bashrc=~/.bash-ssh && history_port=$(basename $(readlink ~/.bash-ssh.history))
local history_remote_port="$($ssh -O forward -R 0:127.0.0.1:$history_port placeholder)"
$ssh placeholder "cat >~/.bash-ssh; ln -nsf /dev/tcp/127.0.0.1/$history_remote_port ~/.bash-ssh.history" < $bashrc
$ssh "$@" -t 'SHELL=~/.bash-ssh; chmod +x $SHELL; bash --rcfile $SHELL -i'
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
O bloco após [-z "$ SSH_TTY"] só funciona na máquina local. Verificamos se a porta está ocupada e, caso contrário, rodamos o netcat nela, cuja saída é redirecionada para um arquivo.
A função update_eternal_history é chamada pouco antes de o prompt bash ser exibido. Esta função verifica se o último comando foi duplicado e, caso contrário, o envia para a porta encaminhada. Se a porta não estiver configurada (no caso de uma máquina local) ou se ocorrer um erro durante o envio, o salvamento vai para um arquivo local.
A função sshb foi suplementada definindo um encaminhamento de porta e criando um link simbólico que será usado por update_eternal_history para enviar dados para o servidor.
Esta solução tem suas desvantagens:
- A porta para netcat está codificada, há uma chance de entrar em conflito
- ( - - ), , ,
Meu próprio .bashrc pode ser visto aqui .
Se você tiver ideias sobre como melhorar a solução proposta, compartilhe nos comentários.
Atualizar. No ubuntu 16.04, encontrei um problema: o netcat congela em várias conexões e ocupa 100% da CPU. Mudei para socat, os testes preliminares mostraram que está tudo bem. Também foi adicionada lógica para gerenciamento do link simbólico, que determina o endereço para onde o histórico é enviado. Ficou assim:
#!/bin/bash
[ -n "$SSH_TTY" ] && [ "${BASH_SOURCE[0]}" == "${0}" ] && exec bash --rcfile "$SHELL" "$@"
[ -z "$PS1" ] && return
[ -z "$SSH_TTY" ] && command -v socat >/dev/null && {
history_port=26574
netstat -lnt|grep -q ":${history_port}\b" || {
umask 077 && socat -u TCP4-LISTEN:$history_port,bind=127.0.0.1,reuseaddr,fork OPEN:$HOME/.bash_eternal_history,creat,append &
}
}
HISTSIZE=$((1024 * 1024))
HISTFILESIZE=$HISTSIZE
HISTTIMEFORMAT='%t%F %T%t'
update_eternal_history() {
local histfile_size=$(stat -c %s $HISTFILE)
history -a
((histfile_size == $(stat -c %s $HISTFILE))) && return
local history_line="${USER}\t${HOSTNAME}\t${PWD}\t$(history 1)"
local history_sink=$(readlink ~/.bash-ssh.history 2>/dev/null)
[ -n "$history_sink" ] && echo -e "$history_line" >"$history_sink" 2>/dev/null && return
local old_umask=$(umask)
umask 077
echo -e "$history_line" >> ~/.bash_eternal_history
umask $old_umask
}
[[ "$PROMPT_COMMAND" == *update_eternal_history* ]] || PROMPT_COMMAND="update_eternal_history;$PROMPT_COMMAND"
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
local bashrc=~/.bashrc
local history_command="rm -f ~/.bash-ssh.history"
[ -r ~/.bash-ssh ] && bashrc=~/.bash-ssh && history_port=$(basename $(readlink ~/.bash-ssh.history 2>/dev/null))
$ssh -fNM "$@"
[ -n "$history_port" ] && {
local history_remote_port="$($ssh -O forward -R 0:127.0.0.1:$history_port placeholder)"
history_command="ln -nsf /dev/tcp/127.0.0.1/$history_remote_port ~/.bash-ssh.history"
}
$ssh placeholder "${history_command}; cat >~/.bash-ssh" < $bashrc
$ssh "$@" -t 'SHELL=~/.bash-ssh; chmod +x $SHELL; bash --rcfile $SHELL -i'
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...