[*nix] Использование локального .bashrc через ssh и консолидация истории выполнения команд
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Если вам приходится работать с большим количеством удаленных машин через ssh то возникает вопрос как унифицировать shell окружение на этих машинах. Копировать заранее .bashrc не очень удобно, а зачастую невозможно. Давайте рассмотрим вариант копирования непосредственно в процессе соединения:
[ -z "$PS1" ] && return
sshb() {
scp ~/.bashrc ${1}:
ssh $1
}
# the rest of the .bashrc
alias c=cat
...
Это очень наивный способ с несколькими очевидными недостатками:
- Можно затереть уже существующий .bashrc
- Вместо одного соединения мы устанавливаем 2
- Как следствие авторизоваться придется тоже 2 раза
- Аргумент функции может быть только адресом удаленной машины
Улучшенный вариант:
[ -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
...
Теперь мы используем только одно соединение за счет мультиплексирования. .bashrc копируется в файл, который не используется bash по умолчанию и мы явно указываем его через опцию --rcfile. Аргументом функции может быть не только адрес удаленной машины, но и другие опции ssh.
На этом в принципе можно было бы и остановиться, но полученное решение обладает неприятным недостатком. Если вы запустите screen или tmux, то будет использоваться тот .bashrc, который находится на удаленной машине и все ваши алиасы и функции потеряются. К счастью это можно побороть. Для этого надо создать скрипт-обертку, который мы объявим нашим новым шеллом. Давайте для простоты предположим, что скрипт-обертка на удаленной машине у нас уже есть и находится в ~/bin/bash-ssh. Выглядит скрипт вот так::
#!/bin/bash
exec /bin/bash --rcfile ~/.bash-ssh “$@”
А .bashrc так:
[ -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
...
Если существует переменная SSH_TTY мы понимаем, что находимся на удаленной машине и переопределяем переменную SHELL. С этого момента при запуске нового интерактивного шелла будет запускаться скрипт, который будет стартовать bash с нестандартным конфигом, сохраненный при установлении ssh сессии.
Для получения удобного рабочего решения осталось придумать как создавать на удаленной машине скрипт-обертку. В принципе можно создавать его в сохраняемом нами конфиге баша вот так:
[ -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
}
Но на самом деле можно обойтись единственным файлом ~/.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
...
Теперь файл ~/.bash-ssh является одновременно и самостоятельным скриптом и конфигом bash. Работает это так. На локальной машине команды после [ -n "$SSH_TTY" ] игнорируются. На удаленной машине функция sshb создает файл ~/.bash-ssh и использует его как конфиг для запуска интерактивной сессии. Конструкция [ "${BASH_SOURCE[0]}" == "${0}" ] позволяет определить подгружается файл другим скриптом или запущен как самостоятельный скрипт. В результате, когда ~/.bash-ssh используется
- как конфиг — exec игнорируется
- как скрипт — управление переходит башу и исполнение ~/.bash-ssh заканчивается тем самым exec-ом.
Теперь при коннекте по ssh ваше окружение везде будет выглядеть одинаково. Так работать гораздо удобнее, но история выполнения команд будет оставаться на машинах, с которыми вы соединялись. Лично мне бы хотелось сохранять историю локально, чтобы иметь возможность освежить в памяти, что именно я делал на каких-то машинах в прошлом. Для того, чтобы это сделать нам нужны следующие компоненты:
- Tcp сервер на локальной машине, который бы принимал данные с сокета и перенаправлял их в файл
- Форвард слушающего порта этого сервера на машину, с которой мы коннектимся по ssh
- PROMPT_COMMAND в установках bash, который бы по завершению команды отправлял обновление истории на отфорварженный порт
Это можно реализовать так:
#!/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
...
Блок после [ -z "$SSH_TTY" ] срабатывает только на локальной машине. Мы проверяем занят ли порт и если нет запускаем на нем netcat, вывод которого перенаправлен в файл.
Функция update_eternal_history вызывается непосредственно перед выводом на экран подсказки bash. Эта функция проверяет не была ли последняя команда дубликатом и если нет отправляет ее на отфорварженный порт. Если порт не сконфигурирован (в случае локальной машины) или если при отправке произошла ошибка сохранение идет в локальный файл.
Функция дополнилась установлением форвардинга порта и созданием симлинки, которая будет использоваться update_eternal_history для отправки данных на сервер.
Это решение не лишено недостатков:
- Порт для netcat захардкожен, есть шанс нарваться на конфликт
- Форвард порта дает возможность любому человеку со злыми намерениями заспамить вашу историю мусорными данными
- Если вы создаете цепочку соединений (машина А-машина Б-машина В), то данные будут нормально передаваться с В на А, но в случае обрыва соединения между А и Б и установления нового соединения Б будет продолжать форвардить старый порт и данные с В не достигнут А, они будут сохраняться на Б
Мой собственный .bashrc можно посмотреть тут.
Если у вас есть идеи как можно улучшить предложенное решение пожалуйста делитесь в комментариях.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, *nix, C] Bison, dynamic linking и… обработка BMP изображений
- [Настройка Linux, Разработка на Raspberry Pi, DIY или Сделай сам] Как сделать из планшетного USB-сканера сетевой с памятью, используя Raspberry Pi. Видеолекция с демонстрацией
- [Open source, *nix] FOSS News №43 – дайджест новостей и других материалов о свободном и открытом ПО за 16-22 ноября 2020 года
- topalias - утилита для генерации коротких алиасов по истории bash/zsh
- [Настройка Linux, *nix, Разработка под Android, Разработка под Linux] Разработка приложения с использованием Python и OpenCV на Android устройстве
- [Системное администрирование] CIFS over SSH штатными средствами Windows 10
- LazySSH, SSH-сервер для запуска временных виртуальных машин
- [Настройка Linux, Atlassian, Софт] Миграция Jira Service Desk из «облака» на сервер
- [Open source, *nix] FOSS News №42 – дайджест новостей и других материалов о свободном и открытом ПО за 9-15 ноября 2020 года
- [*nix] ISH Linux или возможно ли установить и использовать Linux на iOS
Теги для поиска: #_*nix, #_ssh, #_bash, #_*nix
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:33
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Если вам приходится работать с большим количеством удаленных машин через ssh то возникает вопрос как унифицировать shell окружение на этих машинах. Копировать заранее .bashrc не очень удобно, а зачастую невозможно. Давайте рассмотрим вариант копирования непосредственно в процессе соединения: [ -z "$PS1" ] && return
sshb() { scp ~/.bashrc ${1}: ssh $1 } # the rest of the .bashrc alias c=cat ... Это очень наивный способ с несколькими очевидными недостатками:
Улучшенный вариант: [ -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 ... Теперь мы используем только одно соединение за счет мультиплексирования. .bashrc копируется в файл, который не используется bash по умолчанию и мы явно указываем его через опцию --rcfile. Аргументом функции может быть не только адрес удаленной машины, но и другие опции ssh. На этом в принципе можно было бы и остановиться, но полученное решение обладает неприятным недостатком. Если вы запустите screen или tmux, то будет использоваться тот .bashrc, который находится на удаленной машине и все ваши алиасы и функции потеряются. К счастью это можно побороть. Для этого надо создать скрипт-обертку, который мы объявим нашим новым шеллом. Давайте для простоты предположим, что скрипт-обертка на удаленной машине у нас уже есть и находится в ~/bin/bash-ssh. Выглядит скрипт вот так:: #!/bin/bash
exec /bin/bash --rcfile ~/.bash-ssh “$@” А .bashrc так: [ -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 ... Если существует переменная SSH_TTY мы понимаем, что находимся на удаленной машине и переопределяем переменную SHELL. С этого момента при запуске нового интерактивного шелла будет запускаться скрипт, который будет стартовать bash с нестандартным конфигом, сохраненный при установлении ssh сессии. Для получения удобного рабочего решения осталось придумать как создавать на удаленной машине скрипт-обертку. В принципе можно создавать его в сохраняемом нами конфиге баша вот так: [ -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 } Но на самом деле можно обойтись единственным файлом ~/.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 ... Теперь файл ~/.bash-ssh является одновременно и самостоятельным скриптом и конфигом bash. Работает это так. На локальной машине команды после [ -n "$SSH_TTY" ] игнорируются. На удаленной машине функция sshb создает файл ~/.bash-ssh и использует его как конфиг для запуска интерактивной сессии. Конструкция [ "${BASH_SOURCE[0]}" == "${0}" ] позволяет определить подгружается файл другим скриптом или запущен как самостоятельный скрипт. В результате, когда ~/.bash-ssh используется
Теперь при коннекте по ssh ваше окружение везде будет выглядеть одинаково. Так работать гораздо удобнее, но история выполнения команд будет оставаться на машинах, с которыми вы соединялись. Лично мне бы хотелось сохранять историю локально, чтобы иметь возможность освежить в памяти, что именно я делал на каких-то машинах в прошлом. Для того, чтобы это сделать нам нужны следующие компоненты:
#!/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 ... Блок после [ -z "$SSH_TTY" ] срабатывает только на локальной машине. Мы проверяем занят ли порт и если нет запускаем на нем netcat, вывод которого перенаправлен в файл. Функция update_eternal_history вызывается непосредственно перед выводом на экран подсказки bash. Эта функция проверяет не была ли последняя команда дубликатом и если нет отправляет ее на отфорварженный порт. Если порт не сконфигурирован (в случае локальной машины) или если при отправке произошла ошибка сохранение идет в локальный файл. Функция дополнилась установлением форвардинга порта и созданием симлинки, которая будет использоваться update_eternal_history для отправки данных на сервер. Это решение не лишено недостатков:
Мой собственный .bashrc можно посмотреть тут. Если у вас есть идеи как можно улучшить предложенное решение пожалуйста делитесь в комментариях. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:33
Часовой пояс: UTC + 5