Как автоматически установить bash readline в режим vi при входе в систему?

9

Моя команда отвечает за тысячи машин Linux / Unix, поэтому, естественно, учетная запись root «разделяется» между администраторами. Я предпочитаю режим vi, другие предпочитают режим emacs.

Как я могу установить readline bash в режим vi при входе в SSH на любой машине, не заставляя всех остальных также использовать режим vi?

По сути, хотелось бы получить эффект set -o viпосле входа в систему, не вводя его каждый раз и не навязывая его всем остальным (насколько режим emacs меня раздражает, режим vi раздражает их).

Я знаю, что это не будет проблемой, если все будут использовать свои собственные учетные записи с sudo для выполнения привилегированных команд, но из-за обстоятельств, не зависящих от меня, это, к сожалению, не вариант.

Патрик
источник
1
Это не легко. Один из способов - это проанализировать файл журнала sshd и посмотреть, какой ключ использовался для входа в систему. Я надеялся найти решение на стороне клиента, например, способ передать мою локальную конфигурацию readline на удаленную сторону или что-то в этом роде, или какая-то темная магия OpenSSH, которая незаметно исполняется, set -o viпрежде чем дать мне контроль над оболочкой.
Патрик
1
Возможно, вы могли бы использовать сценарий Expect на клиенте, который подключается по ssh к серверу, отправить set -o viкоманду, а затем переключиться в интерактивный режим.
Бармар
1
По крайней мере, OpenSSH sshdустанавливает несколько переменных среды, которые могут помочь вам определить, кто находится на другом конце. Например, SSH_CLIENTсодержит подключающийся IP-адрес (а также исходящий / входящий порт клиента). Привязка к этому ~/.bashrcможет позволить вам делать вещи только для вас .
Сами Лейн
1
Щедрость говорит, что вы ищете «решение на стороне клиента» - что это? SSH на хосте Unix? Шпатлевка? Колибри? Java SSH? Cygwin?
Джефф Шаллер
1
Вы упоминаете десятки тысяч машин. Вы хотите начать с одной машины и добраться до этих машин, или вы хотите иметь возможность переходить с машины на машину на машину, на которой есть режим vi?
Икар

Ответы:

3

Вот глупый способ сделать это, который действительно хорошо работает только с аутентификацией с открытым ключом:

Во-первых, убедитесь, что на вашем локальном компьютере есть nc.

Во-вторых, все еще на вашей локальной машине, сделайте скрипт (я его назову connect-to-server) и поместите его в место, о котором вы ${PATH}знаете *:

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

Затем измените в .bashrcудаленной системе, чтобы включить где-то:

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

Наконец, вернувшись на свой локальный компьютер, отредактируйте, ~/.ssh/configчтобы добавить:

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

Недостатки этого подхода (и почему я называю это глупым):

  • Если требуется фактическая прокси-команда, она усложняется.
  • Если кто-то входит в систему одновременно с вами, есть вероятность, что .yournameфайл еще не будет удален, и в этом случае они set -o viтоже получат .
  • Самое главное, что если вы это сделаете ssh serverNickname command, то commandзапустится, но (поскольку .bashrcон никогда не получен) .yournameфайл останется, поэтому было бы вежливо иметь второй псевдоним в конфигурации ssh, который не использует псевдопрокси.

Фактически, единственным преимуществом этого подхода является то, что вашей sshкоманде не нужно давать никаких дополнительных аргументов.


* Если вы не хотите что-либо менять в удаленных системах, вот альтернативный псевдопрокси, который создает временный .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

Это имеет те же недостатки, что и у другого метода, поэтому вам все равно нужен второй псевдоним в вашей sshконфигурации, который не вызывает псевдопрокси.

Лиса
источник
Очень креативно. Спасибо за предложение. Я уже широко использую ProxyCommand (для некоторых сетей / хостов требуется более 5 прыжков), и это быстро приведет к кошмару конфигурации. Изменить: Глядя на все ответы, я думаю, ваш самый близкий.
Патрик
4

Я бы пошел на:

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

но если вы администратор, вы можете попробовать что-нибудь почище. Например, вы можете использовать SendEnvопцию ssh на стороне клиента для передачи определенной переменной, использовать AcceptEnvв sshdконфигурации (на стороне сервера) ее принятие и на основании этого изменить .bashrcфайл root, чтобы настроить поведение в соответствии со значением переменной.

Это подразумевает изменение sshdконфигурации на всех хостах, а также их .bashrc. Не совсем "автономный" способ сделать, однако ...

Уриэль
источник
3

Для простого решения на стороне клиента:

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

Это не сработает , если при инициализации оболочки сценариев в корне явно , использует set -o emacsили наборы EDITORдля emacs, или если корневого .initrcфайла вызывает emacsпривязки клавиш.

Остальная часть этого ответа касается серверных решений.


Это работает, когда вы sshвходите в машину и затем используете sudo -i:

Для вашего /root/.bashrc:

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

Это позволяет вам иметь личный bashrcфайл с именем, в /root/.bashrc-patrickкотором вы можете делать все что угодно set -o vi.

Комбинируя это с несколько наивным подходом к подбору этого файла rc в зависимости от $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

Очевидно, это работает, только если вы подключаетесь с одного и того же IP-адреса все время ...

Другой подход, который использует поле комментария конкретного используемого ключа SSH, который работает, если вы пересылаете агент SSH на сервер:

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

Это выбирает поле комментария для ключа, который вы использовали для подключения к серверу. Это head -n 1на тот случай, если в authorized_keysфайле окажется несколько ключей .

Затем вы можете использовать, $ssh_commentчтобы выбрать rc-файл для источника, либо напрямую, как в $SUDO_USERподходе выше (в котором комментарий $ssh_commentможет нуждаться в некоторой очистке, если это путь), либо с помощью caseоператора, как в $SSH_CLIENTподходе.

Кусалананда
источник
Соответствие SSH_CLIENTи SUDO_USERто, что я использую в настоящее время, но это требует модификации на стороне сервера, и это не особенно надежно. Я надеялся на чисто клиентское решение. Спасибо за предложение, хотя.
Патрик
3

Если вы действительно хотите сделать это без изменений на стороне сервера, либо:

1) запустить что-то вроде

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

Я не думаю, что документация слишком ясна по этому поводу , но -o optionупоминается и, кажется, работает.

2) Используйте ожидайте:

expectСкрипт:

$ 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

Сделайте его исполняемым и запустите:

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

Это предполагает, что вы можете войти без ввода паролей (для удаленного хоста или для ваших ключей), в противном случае ожидаемый сценарий должен будет принять это во внимание. Но с большим количеством машин вы, вероятно, уже имеете это. Кроме того, я ожидал знака доллара и пробела, отредактируйте это согласно вашей подсказке: "# "возможно.

Хотя если что-то, напечатанное до приглашения, содержит те же символы, вам нужно будет включить в ожидаемую строку что-то более конкретное.

Кроме того, этот скрипт не поддерживает предоставление дополнительных аргументов ssh. Если вы собираетесь дать явную команду для запуска, вам, вероятно, не нужен режим vi, но если вам нужно, скажем, туннелирование портов, это может быть проблемой.


Но в любом случае, я действительно думаю, что это должно быть решено на целевых системах с отдельными учетными записями ( sudoили просто с простым старым UID 0). Персонализированная конфигурация будет полезна и для многих других случаев, и в целом у вас будет куча файлов конфигурации и переменных среды, которые вы хотите установить. (Учтите, что администраторы могут не согласиться с ценностью $EDITOR, содержанием vircили чем-либо еще.)

Также удаление пользователей было бы проще с отдельными учетными записями.

Любой способ синхронизации файлов на всех хостах также легко решает эту проблему, позволяя вам войти в систему с помощью чего-то вроде ssh -t user@host 'patricks_shell.sh'или ssh -t user@host 'bash --rcfile patrick.rc'.

ilkkachu
источник
Я думал об использовании ожидаемого, но, как вы уже указали в своем вопросе, проблема заключается в совпадении с чем-то уникальным для запуска приглашения interact. Он не существует, приглашения могут быть разными, как и motds / выходные данные из профилей. Спасибо за предложение, хотя.
Патрик