
Я написал один скрипт, в котором я хотел бы запустить первый ssh-agent
, чтобы запустить агента в фоновом режиме, и установить соответствующие переменные среды для текущего экземпляра оболочки. Однако во второй части скрипта я также хотел бы добавить свой закрытый ключ SSH, чтобы подключиться к моему серверу.
В настоящее время ни одна из команд в скрипте не работает друг с другом. Может ли кто-нибудь помочь мне правильно понять, что я делаю неправильно?
#!/bin/bash
exec ssh-agent bash
sleep 5s
ssh-add /media/MyUSB/.ssh/id_00123 &
Кроме того, при использовании встроенного отладчика bash
я вижу, что работает только первая часть скрипта (т. е. exec ssh-agent bash
).
решение1
Столько интересных аспектов в таком маленьком сценарии.
Пониманиеssh-agent
Давайте начнем с того, ssh-agent
для чего он предназначен. Вы запускаете ssh-agent
, когда вам нужен процесс, который сидит там, слушает какой-то сокет (это типфайлдля двусторонней межпроцессной коммуникации) и обслуживает запросы от программ, таких как ssh-add
или ssh
, которые подключаются к сокету. Программы будут общаться с агентом и хранить, манипулировать или использовать закрытые ключи.
Любая программа, которая хочет использовать агента, должна знать путь к сокету, который слушает агент. Если программа знает путь, то она может использовать сокет для связи с агентом.
Когда-то было принято решение о дизайне: любая программа, которая хочет узнать путь к сокету агента аутентификации, должна проверить SSH_AUTH_SOCK
переменную в своей собственной среде, значение переменной — это путь. Это было решение (я имею в виду, что вещи можно было бы спроектировать и по-другому, например, программы можно было бы спроектировать так, чтобы они принимали этот путь через аргументы командной строки каждый раз), но это было очень хорошее решение.
Это было очень хорошее решение, поскольку среда по умолчанию наследуется. Это означает, что вам нужно задать SSH_AUTH_SOCK
переменную среды для одного процесса (например, оболочки), и все его потомки унаследуют ее (если только некоторые из них намеренно не решат изменить свою среду или не создадут потомка с измененной средой). Для сравнения: передача пути в качестве аргумента командной строки каждый раз, когда вы хотите запустить что-то, что должно общаться с агентом, требует дополнительного ввода; и вам захочется где-то сохранить путь, так что, вероятно, в переменной в любом случае. Итак, вот, имя переменной стандартизировано, и заинтересованные программы проверяют его автоматически.
Другой вариант — сохранить путь в текстовом файле в фиксированном месте или даже создать сокет в фиксированном месте изначально. Но иногда вы хотите, чтобы некоторые программы использовали одного агента (один сокет), а некоторые другие программы использовали другого агента (другой сокет). Заставить две программы видеть разные файлы в одном месте сложно. Заставить две программы видеть разные переменные окружения легко.
Итак, заинтересованные программы должны проверить SSH_AUTH_SOCK
в своей среде. Как мы можем или что-то еще установить эту переменную на правильное значение в среде процесса? Без отладчика есть два способа:
Либо родитель знает значение, и когда он порождает дочерний элемент, он устанавливает
SSH_AUTH_SOCK
правильное значение в среде для дочернего элемента (акт наследования без измененийSSH_AUTH_SOCK
от родителя может быть интерпретирован как «родитель устанавливает это, ничего не делая»);или процесс узнает значение каким-то другим способом и изменяет свою собственную среду.
Поэтому ssh-agent
поддерживаются два способа запуска:
-
ssh-agent command …
Здесь
ssh-agent
создается сокет и подготавливается к обслуживанию будущих программ, подключающихся к сокету. Затем он запускаетсяcommand …
как его потомок сSSH_AUTH_SOCK
правильным значением в среде для потомка. Потомок (или любой потомок, который наследует переменную) может легко найти сокет, но другие процессы не так легко. Когда завершаетсяcommand
, то же самое происходитssh-agent
(даже если есть внуки). -
ssh-agent # but don't use it exactly this way
Здесь
ssh-agent
происходит ответвление в фоновый режим, т. е. он создает дочернюю копию себя и не ждет ее выхода. Дочерний процесс отсоединяется от стандартных потоков родителя и от терминала, он не выйдет сам по себе. Дочерний процесс будет настоящим агентом, который останется. Родительский процесс выйдет сам по себе, но прежде чем это произойдет, печатается код оболочки. Код оболочки, когда оценивается оболочкой, заставляет оболочку изменять свою собственную среду, поэтомуSSH_AUTH_SOCK
с правильным значением помещается туда.Но оболочка должнаоцениватьвывод, а не просто запускssh-agent
, поэтому правильный способ такой:eval "$(ssh-agent)"
После этого запущенная оболочка
eval
имеет правильную переменную (фактически: переменные) в своей среде, и с этого момента команды типаssh-add
run из этой оболочки будут находить агента, поскольку они наследуют переменную. Выход из оболочки не завершает работу агента, поэтому в какой-то момент перед выходом из оболочки вы можете вызватьssh-agent -k
(или, если вы также хотите сбросить переменные:eval "$(ssh-agent -k)"
). Агент, для которого нет процесса, содержащего правильное значение ,SSH_AUTH_SOCK
практически бесполезен.
Что не так с вашим сценарием?
И теперь – наконец – ваш сценарий. Это ваш сценарий:
#!/bin/bash exec ssh-agent bash sleep 5s ssh-add /media/MyUSB/.ssh/id_00123 &
Первое, что делает скрипт, это exec ssh-agent bash
. exec
сообщает оболочке, интерпретирующей скрипт, заменить себя командой, которая есть ssh-agent bash
. Оболочка делает это и становится , ssh-agent
которая начинает новый bash
(это метод 1 из выше). Он bash
содержит правильное значение SSH_AUTH_SOCK
, он интерактивный, он выводит приглашение и позволяет вам запускать команды (включая команды, которым нужно SSH_AUTH_SOCK
). Если ваша исходная интерактивная оболочка была , bash
то вы можете упустить тот факт, что теперь вы находитесь в отдельном bash
. Вы можете интерпретировать существование SSH_AUTH_SOCK
как подтверждение того, что ssh-agent
изменило среду вашей исходной оболочки. Нет, вы все еще находитесь в середине своего скрипта.
Ну, не совсем посередине. Если выйти из этого bash
, то sleep
и остальное не будет выполнено, потому что интерпретирующая скрипт оболочка заменила себя на ssh-agent
. В каком-то смысле вы один exit
перед концом скрипта.
Если ваш метод запуска скрипта был как ./myscript
, то exit
вернет вас в исходную оболочку. Если ваш метод был как. ./myscript
илиsource myscript
то exit
будет действовать так, как будто вы вышли из исходной оболочки, поскольку исходная оболочка была оболочкой, интерпретирующей сценарий, и заменила себя на ssh-agent
оболочку, которая собирается выйти exit
из текущей оболочки; это может усилить впечатление, что вы находились в исходной оболочке (и теперь выходите из нее).
Исправление
В вопросе вы четко обозначили свою цель:
[…] соответствующие переменные среды для текущего экземпляра оболочки. […]
Чтобы изменить окружение текущей оболочки, скрипт должен использовать метод 2 из вышеприведенного. Текущая оболочка должна быть интерпретирующей оболочкой, т. е. скрипт должен быть исходным. Оболочка не должна ни exec
к чему, так как вы не хотите, чтобы оболочка была заменена чем-либо. Пример исправления:
#!/usr/bin/false
[ -n "$SSH_AUTH_SOCK" ] || eval "$(ssh-agent)"
ssh-add /media/MyUSB/.ssh/id_00123
Вот еще кое-что, что было улучшено:
#!/usr/bin/false
так как shebang гарантирует, что скрипт ничего не сделает и не выполнит свою задачу, если вы (непреднамеренно) запустите его вместо того, чтобы получить его. Другие стратегии здесь:Стратегия, позволяющая забыть запустить скриптsource
.Без всяких уловокили с shebang, указывающим наbash
,sh
или другую совместимую оболочку, выполненный скрипт (не исходный) запустит нового агента, добавит к нему ключ и завершит работу. Все это не затрагивает среду вашей текущей оболочки, поэтому агент будет сидеть там напрасно, почти недоступный. Вам нужно будет приложить некоторые усилия, чтобы найти и убить его, или некоторые усилия, чтобы найти его сокет и вручную настроитьSSH_AUTH_SOCK
в среде вашей оболочки; или вы просто оставите его в покое.false
поскольку shebang предотвращает этот неудобный сценарий.[ -n "$SSH_AUTH_SOCK" ]
проверяет,$SSH_AUTH_SOCK
расширяется ли до непустой строки. Пустая строка указывает на отсутствие доступного агента, а непустая строка указывает на то, что агент, вероятно, есть. Скрипт начинает новый,ssh-agent
только если строка пуста. Это базовая мера предосторожности против сценария, когда вы (непреднамеренно) вызываете скрипт во второй раз, создаете нового агента аутентификации и теряете переменные, связанные с предыдущим агентом, который будет продолжать работать бесполезно.Нет необходимости в том, чтобы
sleep
.ssh-agent
в нашем скрипте выходы происходили, когда агент (т.е. его потомок в фоновом режиме) готов. Вы можете сделать этоssh-add
сразу.ssh-add
здесь как синхронная команда. Запуск ее асинхронно (с помощью&
, как вы пытались сделать) вряд ли сэкономит вам много времени. Вы можете попробовать. Но вы, скорее всего, получите скрипт из интерактивной оболочки с включенным управлением заданиями и, таким образом&
(если вы поместите его туда), засорите свой терминал сообщением типа[1]+ Done …
.