Comment configurer bash readline en mode vi automatiquement lors de la connexion à un système?

9

Mon équipe est responsable de milliers de machines Linux / Unix, donc naturellement le compte root est "partagé" entre les administrateurs. Je préfère le mode vi, d'autres préfèrent le mode emacs.

Comment puis-je définir la ligne de lecture de bash en mode vi lors de la connexion SSH à n'importe quelle machine, sans forcer tout le monde à utiliser également le mode vi?

Essentiellement, j'aimerais avoir l'effet set -o viaprès la connexion sans avoir à le taper à chaque fois, et sans l'imposer à tout le monde (autant que le mode emacs me dérange, le mode vi le gêne).

Je sais que ce ne serait pas un problème si tout le monde utilisait ses propres comptes avec sudo pour exécuter des commandes privilégiées, mais en raison de circonstances indépendantes de ma volonté, ce n'est malheureusement pas une option.

Patrick
la source
1
Ce n'est pas facile. Une façon consiste à analyser le fichier journal de sshd et à voir quelle clé a été utilisée pour se connecter. J'espérais une solution côté client, par exemple un moyen de passer ma configuration de ligne de lecture locale du côté distant ou quelque chose comme ça, ou une magie sombre d'OpenSSH qui s'exécute silencieusement set -o viavant de me donner le contrôle du shell.
Patrick
1
Vous pourriez peut-être utiliser un script Expect sur le client qui ssh est dans le serveur, envoyer la set -o vicommande, puis passer en mode interactif.
Barmar
1
Au moins, OpenSSH sshdconfigure plusieurs variables d'environnement qui pourraient vous aider à déterminer qui est à l'autre bout. Par exemple, SSH_CLIENTcontient l'adresse IP de connexion (et le port sortant / entrant du client également). Jouer avec cela ~/.bashrcpourrait vous permettre de faire des choses juste pour vous .
Sami Laine,
1
La prime dit que vous cherchez une "solution côté client" - qu'est-ce que c'est? ssh sur un hôte Unix? Mastic? Colibri? Java SSH? Cygwin?
Jeff Schaller
1
Vous mentionnez des dizaines de milliers de machines. Voulez-vous démarrer à partir d' une machine et accéder à ces machines, ou voulez-vous pouvoir sauter de machine en machine en machine avec le mode vi avec vous?
icarus

Réponses:

3

Voici une façon idiote de le faire, qui ne fonctionne vraiment bien qu'avec l'authentification par clé publique:

Tout d'abord, assurez-vous que votre machine locale est ncdessus.

Deuxièmement, toujours sur votre machine locale, créez un script (je l'appellerai connect-to-server) et placez-le dans un endroit que vous ${PATH}connaissez *:

#!/bin/sh
# connect-to-server
ssh -q server-hostname "touch .yourname" </dev/null >/dev/null 2>&1
nc server-hostname 22

Ensuite, modifiez le .bashrcsur le système distant pour inclure quelque part:

# partial .bashrc
if [ -f "${HOME}/.yourname" ]; then
  rm "${HOME}/.yourname"
  set -o vi
fi

Enfin, de retour sur votre machine locale, modifiez ~/.ssh/configpour ajouter:

# partial ssh config
Host serverNickname
  Hostname server-hostname
  ProxyCommand connect-to-server

Inconvénients de cette approche (et pourquoi je l'appelle stupide):

  • Si une commande proxy réelle est nécessaire, elle devient plus compliquée.
  • Si quelqu'un d'autre se connecte en même temps que vous, il est possible que le .yournamefichier n'ait pas encore été supprimé, auquel cas il le recevra set -o viégalement.
  • Plus important encore, si vous le faites ssh serverNickname command, commands'exécutera, mais (puisqu'il .bashrcn'est jamais fourni), le .yournamefichier reste, il serait donc poli d'avoir un deuxième alias dans votre configuration ssh qui n'utilise pas le pseudo-proxy.

En fait, le seul avantage de cette approche est que votre sshcommande n'a pas besoin de recevoir d'arguments supplémentaires.


* Si vous ne voulez rien changer sur les systèmes distants, voici un pseudo-proxy alternatif qui crée un temporaire .bashrc:

#!/bin/sh
# connect-to-server
ssh -q server-hostname 'ln .bashrc .bashrc.real; cat .bashrc.real <(printf "set -o vi; ln -f .bashrc.real .bashrc\n") >.bashrc.yourname; ln -f .bashrc.yourname .bashrc' </dev/null >/dev/null 2>&1
nc server-hostname 22

Cela a tous les mêmes inconvénients de l'autre méthode, vous voudrez donc toujours un deuxième alias dans votre sshconfiguration qui n'invoque pas le pseudo-proxy.

Renard
la source
Très créatif. Merci pour la suggestion. J'utilise déjà largement ProxyCommand (certains réseaux / hôtes nécessitent 5+ sauts pour atteindre) et cela entraînerait rapidement un cauchemar de configuration. Edit: En regardant toutes les réponses, je pense que la vôtre se rapproche le plus.
Patrick
4

J'irais pour:

ssh server -t "bash --login -o vi"

mais si vous êtes administrateur, vous pouvez essayer quelque chose de plus propre. Par exemple, vous pouvez utiliser l' SendEnvoption ssh côté client pour transmettre une variable spécifique, utiliser AcceptEnvdans la sshdconfiguration (côté serveur) pour l'accepter, et en fonction de cela, modifier le .bashrcfichier racine pour ajuster le comportement en fonction de la valeur de la variable.

Cela implique de changer la sshdconfiguration sur tous les hôtes ainsi que sur leurs .bashrc. Ce n'est pas exactement une façon "autonome" de faire, cependant ...

Uriel
la source
3

Pour une solution côté client simple:

alias connect='ssh -t root@server "bash -o vi"'

Cela échouera si les scripts d'initialisation du shell de la racine utilisent explicitement set -o emacsou sont définis EDITORsur emacs, ou si le .initrcfichier de la racine appelle les emacsraccourcis clavier.

Le reste de cette réponse concerne les solutions côté serveur.


Cela fonctionne lorsque vous entrez sshdans la machine et utilisez ensuite sudo -i:

Pour votre /root/.bashrc:

if [[ -n "$SUDO_USER" ]] && [[ -f /root/.bashrc-"$SUDO_USER" ]]; then
  source /root/.bashrc-"$SUDO_USER"
fi

Cela vous permet d'avoir un bashrcfichier personnel appelé /root/.bashrc-patrickdans lequel vous pouvez faire ce que vous voulez set -o vi.

Combiner cela avec une approche quelque peu naïve pour choisir ce fichier rc en fonction de $SSH_CLIENT:

if [[ -n "$SUDO_USER" ]]; then
  person="$SUDO_USER"
elif [[ -n "$SSH_CLIENT" ]]; then

  case "$SSH_CLIENT" in
    192.168.216.100*)  person="joe" ;;
    192.168.216.120*)  person="patrick" ;;
    192.168.216.150*)  person="lindsey" ;;
  esac

fi

if [[ -n "$person" ]] && [[ -f /root/.bashrc-"$person" ]]; then
  source /root/.bashrc-"$person"
fi

Cela ne fonctionne évidemment que si vous vous connectez à partir de la même adresse IP tout le temps ...

Une autre approche qui utilise le champ de commentaire de la clé SSH particulière que vous utilisez, qui fonctionne si vous transférez l'agent SSH vers le serveur:

ssh_comment="$( ssh-add -L | grep -f /root/.ssh/authorized_keys | awk '{ print $NF '} | head -n 1 )"

Cela sélectionne le champ de commentaire pour la clé que vous avez utilisée pour vous connecter au serveur. Le head -n 1est là au cas où vous auriez plusieurs clés dans le authorized_keysfichier.

Vous pouvez ensuite utiliser $ssh_commentpour sélectionner un fichier rc à source, soit directement comme avec l' $SUDO_USERapproche ci-dessus (dans laquelle le commentaire dans $ssh_commentpeut nécessiter un nettoyage si c'est un nom de chemin), ou via une caseinstruction comme avec l' $SSH_CLIENTapproche.

Kusalananda
la source
Correspondant SSH_CLIENTet SUDO_USERc'est ce que j'utilise actuellement, mais cela nécessite une modification côté serveur et ce n'est pas particulièrement fiable. J'espérais une solution purement côté client. Merci pour la suggestion.
Patrick
3

Si vous voulez vraiment le faire sans modification côté serveur, soit:

1) Exécutez quelque chose comme

$ ssh user@host -t 'bash -l -o vi' 

Je ne pense pas que la documentation soit trop claire à ce sujet, mais elle -o optionest mentionnée et semble fonctionner.

2) Utilisation attendez:

Le expectscript:

$ cat bashsetup.expect
#!/usr/bin/expect -f 

set user [lindex $argv 0];
set host [lindex $argv 1];

spawn ssh -l $user $host
expect "$ "
send "set -o vi\n"
interact

Rendez-le exécutable et exécutez:

$ ./bashsetup.expect user testhost
spawn ssh -l user testhost
[motd, blahblah...]
user@testhost ~$ set -o vi
user@testhost ~$ 

Cela suppose que vous pouvez vous connecter sans saisir de mot de passe (pour l'hôte distant ou pour vos clés), sinon le script attendu devrait en tenir compte. Mais avec beaucoup de machines, vous avez probablement déjà cela. De plus, je m'attendais à un signe dollar et à un espace, modifiez-le selon votre invite: "# "peut - être.

Bien que si quelque chose imprimé avant l'invite inclut ces mêmes caractères, vous devrez inclure quelque chose de plus spécifique dans la chaîne attendue.

En outre, ce script ne prend pas en charge la fourniture d'arguments supplémentaires à ssh. Si vous allez donner une commande explicite à exécuter, vous n'avez probablement pas besoin de vi-mode, mais si vous avez besoin par exemple de tunneling de port, cela pourrait être un problème.


Mais en tout cas, je pense vraiment que cela devrait être résolu sur les systèmes cibles avec des comptes séparés ( sudoou tout simplement l'ancien UID 0). Une configuration personnalisée serait également utile dans de nombreux autres cas, et en général, vous auriez un tas de fichiers de configuration et de variables d'environnement que vous souhaitez définir. (Considérez que les administrateurs peuvent ne pas s'entendre sur la valeur $EDITOR, le contenu vircou quoi que ce soit.)

Il serait également plus facile de supprimer des utilisateurs avec des comptes séparés.

Toute façon de synchroniser des fichiers sur tous les hôtes résoudrait également trivialement cela en vous permettant de vous connecter avec quelque chose comme ssh -t user@host 'patricks_shell.sh'ou ssh -t user@host 'bash --rcfile patrick.rc'.

ilkkachu
la source
J'ai pensé à utiliser expect, mais comme vous l'avez déjà souligné dans votre question, le problème correspond à quelque chose d'unique pour que l'invite démarre interact. Il n'existe pas, les invites peuvent être très différentes, tout comme les motds / sorties des profils. Merci pour la suggestion.
Patrick