Команда Net::SSH sudo зависает после ввода пароля

Я пытался написать небольшую библиотеку, используя Thor, чтобы помочь мне в быстром создании новых проектов и сайтов. Я написал этот маленький метод:

def ssh(cmd)
  Net::SSH.start( server_ip, user, :port => port) do |session|
    session.exec cmd
  end
end

просто чтобы помочь мне в выполнении быстрых команд на удаленных серверах, когда это необходимо.

Проблема в том, что когда мне нужно запустить команду под sudo на удаленном конце, сценарий просто зависает на мне. Например, при выполнении этого...

ssh("sudo cp #{file_from_path} #{file_to_path}" )

Скрипт подскажет мне пароль

[sudo] password for user:

Но потом все набирает текст после ввода.

Кто-нибудь случайно узнает, почему он зависает именно так, и что я могу сделать, чтобы запустить команду sudo на удаленном сервере под Net::SSH (или какой-нибудь другой вариант)?

* примечание: перед предложением я изначально начал писать эту библиотеку как рецепт под Capistrano, пока не наткнулся на Тора и подумал, что это будет хороший шанс опробовать его. Я не против того, чтобы переключать все это обратно на Capistrano, если это необходимо, но я был бы очень удивлен, если бы не было простого способа запуска команд sudo на удаленном сервере.

6 ответов

Решение

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

Например:

(Эта часть действительно зависит от программного обеспечения сервера / клиента)

$ ssh-keygen
$ scp .ssh/id-dsa.pub server:
$ ssh server
server$ cat id-dsa.pub >> .ssh/authorizedkeys

$ scp -c "ls"

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

Надеюсь, это поможет кому-то искать. Мне также нужно было sudo во время развертывания (перезапуск тонких экземпляров)

# deploy.rake
require 'net/ssh'

# INITIALIZE CONSTANTS HERE
HOST = 'yourwebsite.com'
USER = 'admin'
PASSWORD = 'your server password' # or use ENV variables?
# etc.

namespace :deploy do 
  namespace :staging do  
    task :restart do
      commands = [
        "cd #{PATH_TO_STAGING_APP} && git checkout master",
        "git reset --hard HEAD",
        "git pull origin master",
        "bundle install --without test development",
        "sudo thin restart -C /etc/thin/#{STAGING_APP}.yml"
      ]

      Net::SSH.start(HOST, USER, :password => PASSWORD) do |ssh|
        ssh.open_channel do |channel|
          channel.request_pty do |ch, success|
            if success
              puts "Successfully obtained pty"
            else
              puts "Could not obtain pty"
            end
          end

          channel.exec(commands.join(';')) do |ch, success|
            abort "Could not execute commands!" unless success

            channel.on_data do |ch, data|
              puts "#{data}"
              channel.send_data "#{PASSWORD}\n" if data =~ /password/
            end

            channel.on_extended_data do |ch, type, data|
              puts "stderr: #{data}"
            end

            channel.on_close do |ch|
              puts "Channel is closing!"
            end
          end
        end
        ssh.loop
      end
    end
  end
end

Обратите внимание, что один канал может выполнять только одну команду. Поэтому я связал команды вместе с commands.join(';')

Ссылка: Net:: SSH:: Connection:: Channel

Вот возможное решение:

def ssh(cmd)
  Net::SSH.start( server_ip, user, :port => port) do |session|
    result = nil
    session.exec!(cmd) do |channel, stream, data|
      if data =~ /^\[sudo\] password for user:/
        channel.send_data 'your_sudo_password'
      else
        result << data
      end
    end
    result # content of 'cmd' result
  end
end

Но с этим решением ваш корневой пароль записан в открытом виде в файле сценария...

Можно настроить с помощью net/ssh, но это действительно слишком много усилий, просто выполнить ssh команда как это:

system("ssh", "-t", "#{user}@#{host}", "sudo cp #{file_from_path} #{file_to_path}")

это -t значит выделить tty. Без него все равно будет работать, но ваш пароль будет виден в открытом виде.

(Я предполагаю, что вы намерены ввести пароль sudo вручную. Если нет, перейдите по пути авторизованных ключей).

Я обманул, добавив это:

sudouser  ALL=(ALL:ALL) NOPASSWD: ALL

в /etc/sudoers

Также обязательно используйте visudo при редактировании!!!

На самом деле, в конце концов, я сделал что-то очень похожее на предложение Рибы (хотя я думаю, что его код гораздо лучше и умнее)

def ssh(env, cmd, opts={})
    Net::SSH.start( config[env]["ip"], config[env]["user"], :port => config[env]["port"]) do |session|
        cmd = "sudo #{cmd}" if opts[:sudo]
        print cmd
        session.exec cmd
    end
end
Другие вопросы по тегам