{"id":13747518,"url":"https://github.com/capistrano/sshkit","last_synced_at":"2025-05-11T14:00:06.409Z","repository":{"id":2680232,"uuid":"3672542","full_name":"capistrano/sshkit","owner":"capistrano","description":"A toolkit for deploying code and assets to servers in a repeatable, testable, reliable way.","archived":false,"fork":false,"pushed_at":"2025-02-09T01:24:27.000Z","size":1565,"stargazers_count":1170,"open_issues_count":45,"forks_count":254,"subscribers_count":30,"default_branch":"master","last_synced_at":"2025-04-30T14:19:04.750Z","etag":null,"topics":["devops","ruby","ssh"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/capistrano.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-03-09T16:37:07.000Z","updated_at":"2025-04-26T15:51:13.000Z","dependencies_parsed_at":"2023-11-18T00:57:31.261Z","dependency_job_id":"ffd7e98e-2816-4e74-8881-d1d20fdd6482","html_url":"https://github.com/capistrano/sshkit","commit_stats":{"total_commits":649,"total_committers":130,"mean_commits":4.992307692307692,"dds":0.7026194144838213,"last_synced_commit":"d8acf40b62b78f2ab335b767f98abc5c9ba56b1c"},"previous_names":[],"tags_count":84,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capistrano%2Fsshkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capistrano%2Fsshkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capistrano%2Fsshkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/capistrano%2Fsshkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/capistrano","download_url":"https://codeload.github.com/capistrano/sshkit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251840545,"owners_count":21652376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","ruby","ssh"],"created_at":"2024-08-03T06:01:32.104Z","updated_at":"2025-05-11T14:00:06.389Z","avatar_url":"https://github.com/capistrano.png","language":"Ruby","readme":"![SSHKit Logo](https://raw.github.com/leehambley/sshkit/master/examples/images/logo.png)\n\n**SSHKit** is a toolkit for running commands in a structured way on one or\nmore servers.\n\n[![Gem Version](https://badge.fury.io/rb/sshkit.svg)](https://rubygems.org/gems/sshkit)\n[![Build Status](https://github.com/capistrano/sshkit/actions/workflows/ci.yml/badge.svg)](https://github.com/capistrano/sshkit/actions/workflows/ci.yml)\n\n## Example\n\n - Connect to 2 servers\n - Execute commands as `deploy` user with `RAILS_ENV=production`\n - Execute commands in serial (default is `:parallel`)\n\n```ruby\nrequire 'sshkit'\nrequire 'sshkit/dsl'\ninclude SSHKit::DSL\n\non [\"1.example.com\", \"2.example.com\"], in: :sequence do |host|\n  puts \"Now executing on #{host}\"\n  within \"/opt/sites/example.com\" do\n    as :deploy  do\n      with RAILS_ENV: 'production' do\n        execute :rake, \"assets:precompile\"\n        execute :rails, \"runner\", \"S3::Sync.notify\"\n      end\n    end\n  end\nend\n```\n\nMany other examples are in [EXAMPLES.md](EXAMPLES.md).\n\n## Basic usage\n\nThe `on()` method is used to specify the backends on which you'd like to run the commands.\nYou can pass one or more hosts as parameters; this runs commands via SSH. Alternatively you can\npass `:local` to run commands locally. By default SSKit will run the commands on all hosts in\nparallel.\n\n### Running commands\n\nAll backends support the `execute(*args)`, `test(*args)` \u0026 `capture(*args)` methods\nfor executing a command. You can call any of these methods in the context of an `on()`\nblock.\n\n**Note: In SSHKit, the first parameter of the `execute` / `test` / `capture` methods\nhas a special significance. If the first parameter isn't a Symbol,\nSSHKit assumes that you want to execute the raw command and the\n`as` / `within` / `with` methods, `SSHKit.config.umask` and [the comand map](#the-command-map)\nhave no effect.**\n\nTypically, you would pass a Symbol for the command name and it's args as follows:\n\n```ruby\non '1.example.com' do\n  if test(\"[ -f somefile.txt ]\")\n    execute(:cp, 'somefile.txt', 'somewhere_else.txt')\n  end\n  ls_output = capture(:ls, '-l')\nend\n```\n\nBy default the `capture` methods strips whitespace. If you need to preserve whitespace\nyou can pass the `strip: false` option: `capture(:ls, '-l', strip: false)`\n\n### Transferring files\n\nAll backends also support the `upload!` and `download!` methods for transferring files.\nFor the remote backend, the file is transferred with scp by default, but sftp is also\nsupported. See [EXAMPLES.md](EXAMPLES.md) for details.\n\n```ruby\non '1.example.com' do\n  upload! 'some_local_file.txt', '/home/some_user/somewhere'\n  download! '/home/some_user/some_remote_file.txt', 'somewhere_local', log_percent: 25\nend\n```\n\n### Users, working directories, environment variables and umask\n\nWhen running commands, you can tell SSHKit to set up the context for those\ncommands using the following methods:\n\n```ruby\nas(user: 'un', group: 'grp') { execute('cmd') } # Executes sudo -u un -- sh -c 'sg grp cmd'\nwithin('/somedir') { execute('cmd') }           # Executes cd /somedir \u0026\u0026 cmd\nwith(env_var: 'value') { execute('cmd') }       # Executes ENV_VAR=value cmd\nSSHKit.config.umask = '077'                     # All commands are executed with umask 077 \u0026\u0026 cmd\n```\n\nThe `as()` / `within()` / `with()` are nestable in any order, repeatable, and stackable.\n\nWhen used inside a block in this way, `as()` and `within()` will guard\nthe block they are given with a check.\n\nIn the case of `within()`, an error-raising check will be made that the directory\nexists; for `as()` a simple call to `sudo -u \u003cuser\u003e -- sh -c \u003ccommand\u003e'` wrapped in a check for\nsuccess, raising an error if unsuccessful.\n\nThe directory check is implemented like this:\n\n    if test ! -d \u003cdirectory\u003e; then echo \"Directory doesn't exist\" 2\u003e\u00261; false; fi\n\nAnd the user switching test is implemented like this:\n\n    if ! sudo -u \u003cuser\u003e whoami \u003e /dev/null; then echo \"Can't switch user\" 2\u003e\u00261; false; fi\n\nAccording to the defaults, any command that exits with a status other than 0\nraises an error (this can be changed). The body of the message is whatever was\nwritten to *stdout* by the process. The `1\u003e\u00262` redirects the standard output\nof echo to the standard error channel, so that it's available as the body of\nthe raised error.\n\nHelpers such as `runner()` and `rake()` which expand to `execute(:rails, \"runner\", ...)` and\n`execute(:rake, ...)` are convenience helpers for Ruby, and Rails based apps.\n\n### Verbosity / Silence\n\n - raise verbosity of a command: `execute \"echo DEAD\", verbosity: :ERROR`\n - hide a command from output: `execute \"echo HIDDEN\", verbosity: :DEBUG`\n\n## Parallel\n\nNotice on the `on()` call the `in: :sequence` option, the following will do\nwhat you might expect:\n\n```ruby\non(in: :parallel) { ... }\non(in: :sequence, wait: 5) { ... }\non(in: :groups, limit: 2, wait: 5) { ... }\n```\n\nThe default is to run `in: :parallel` which has no limit. If you have 400 servers,\nthis might be a problem and you might better look at changing that to run in\n`groups`, or `sequence`.\n\nGroups were designed in this case to relieve problems (mass Git checkouts)\nwhere you rely on a contested resource that you don't want to DDOS by hitting\nit too hard.\n\nSequential runs were intended to be used for rolling restarts, amongst other\nsimilar use-cases.\n\nThe default runner can be set with the `SSHKit.config.default_runner` option.  For\nexample:\n ```ruby\nSSHKit.config.default_runner = :parallel\nSSHKit.config.default_runner = :sequence\nSSHKit.config.default_runner = :groups\nSSHKit.config.default_runner = MyRunner # A custom runner\n```\n\nIf more control over the default runner is needed, the `SSHKit.config.default_runner_config`\ncan be set.\n```ruby\n# Set the runner and then the config for the runner\nSSHKit.config.default_runner = :sequence\nSSHKit.config.default_runner_config = { wait: 5 }\n\n# Or just set everything once\nSSHKit.config.default_runner_config = { in: :sequence, wait: 5 }\n```\n\n## Synchronisation\n\nThe `on()` block is the unit of synchronisation, one `on()` block will wait\nfor all servers to complete before it returns.\n\nFor example:\n\n```ruby\nall_servers = %w{one.example.com two.example.com three.example.com}\nsite_dir    = '/opt/sites/example.com'\n\n# Let's simulate a backup task, assuming that some servers take longer\n# then others to complete\non all_servers do |host|\n  within site_dir do\n    execute :tar, '-czf', \"backup-#{host.hostname}.tar.gz\", 'current'\n    # Will run: \"/usr/bin/env tar -czf backup-one.example.com.tar.gz current\"\n  end\nend\n\n# Now we can do something with those backups, safe in the knowledge that\n# they will all exist (all tar commands exited with a success status, or\n# that we will have raised an exception if one of them failed.\non all_servers do |host|\n  within site_dir do\n    backup_filename = \"backup-#{host.hostname}.tar.gz\"\n    target_filename = \"backups/#{Time.now.utc.iso8601}/#{host.hostname}.tar.gz\"\n    puts capture(:s3cmd, 'put', backup_filename, target_filename)\n  end\nend\n```\n\n## The Command Map\n\nIt's often a problem that programmatic SSH sessions don't have the same environment\nvariables as interactive sessions.\n\nA problem often arises when calling out to executables expected to be on\nthe `$PATH`.  Under conditions without dotfiles or other environmental\nconfiguration, `$PATH` may not be set as expected, and thus executables are not found where expected.\n\nTo try and solve this there is the `with()` helper which takes a hash of variables and makes them\navailable to the environment.\n\n```ruby\nwith path: '/usr/local/bin/rbenv/shims:$PATH' do\n  execute :ruby, '--version'\nend\n```\n\nWill execute:\n\n    ( PATH=/usr/local/bin/rbenv/shims:$PATH /usr/bin/env ruby --version )\n\nBy contrast, the following won't modify the command at all:\n\n\n```ruby\nwith path: '/usr/local/bin/rbenv/shims:$PATH' do\n  execute 'ruby --version'\nend\n```\n\nWill execute, without mapping the environmental variables, or querying the command map:\n\n    ruby --version\n\n(This behaviour is sometimes considered confusing, but it has mostly to do with shell escaping: in the case of whitespace in your command, or newlines, we have no way of reliably composing a correct shell command from the input given.)\n\n**Often more preferable is to use the *command map*.**\n\nThe *command map* is used by default when instantiating a *Command* object\n\nThe *command map* exists on the configuration object, and in principle is\nquite simple, it's a *Hash* structure with a default key factory block\nspecified, for example:\n\n```ruby\nputs SSHKit.config.command_map[:ruby]\n# =\u003e /usr/bin/env ruby\n```\n\nTo make clear the environment is being deferred to, the `/usr/bin/env` prefix is applied to all commands.\nAlthough this is what happens anyway when one would simply attempt to execute `ruby`, making it\nexplicit hopefully leads people to explore the documentation.\n\nOne can override the hash map for individual commands:\n\n```ruby\nSSHKit.config.command_map[:rake] = \"/usr/local/rbenv/shims/rake\"\nputs SSHKit.config.command_map[:rake]\n# =\u003e /usr/local/rbenv/shims/rake\n```\n\nAnother opportunity is to add command prefixes:\n\n```ruby\nSSHKit.config.command_map.prefix[:rake].push(\"bundle exec\")\nputs SSHKit.config.command_map[:rake]\n# =\u003e bundle exec rake\n\nSSHKit.config.command_map.prefix[:rake].unshift(\"/usr/local/rbenv/bin exec\")\nputs SSHKit.config.command_map[:rake]\n# =\u003e /usr/local/rbenv/bin exec bundle exec rake\n```\n\nOne can also override the command map completely, this may not be wise, but it\nwould be possible, for example:\n\n```ruby\nSSHKit.config.command_map = Hash.new do |hash, command|\n  hash[command] = \"/usr/local/rbenv/shims/#{command}\"\nend\n```\n\nThis would effectively make it impossible to call any commands which didn't\nprovide an executable in that directory, but in some cases that might be\ndesirable.\n\n*Note:* All keys should be symbolised, as the *Command* object will symbolize it's\nfirst argument before attempting to find it in the *command map*.\n\n## Interactive commands\n\u003e (Added in version 1.8.0)\n\nBy default, commands against remote servers are run in a *non-login, non-interactive* ssh session.\nThis is by design, to try and isolate the environment and make sure that things work as expected,\nregardless of any changes that might happen on the server side. This means that,\nalthough the server may have prompted you, and be waiting for it,\n**you cannot send data to the server by typing into your terminal window**.\nWherever possible, you should call commands in a way that doesn't require interaction\n(eg by specifying all options as command arguments).\n\nHowever in some cases, you may want to programmatically drive interaction with a command\nand this can be achieved by specifying an `:interaction_handler` option when you `execute`, `capture` or `test` a command.\n\n**It is not necessary, or desirable to enable `Netssh.config.pty` to use the `interaction_handler` option.\nOnly enable `Netssh.config.pty` if the command you are calling won't work without a pty.**\n\nAn `interaction_handler` is an object which responds to `on_data(command, stream_name, data, channel)`.\nThe `interaction_handler`'s `on_data` method will be called each time `stdout` or `stderr` data is available from\nthe server. Data can be sent back to the server using the `channel` parameter. This allows scripting of command\ninteraction by responding to `stdout` or `stderr` lines with any input required.\n\nFor example, an interaction handler to change the password of your linux user using the `passwd` command could look like this:\n\n```ruby\nclass PasswdInteractionHandler\n  def on_data(command, stream_name, data, channel)\n    puts data\n    case data\n      when '(current) UNIX password: '\n        channel.send_data(\"old_pw\\n\")\n      when 'Enter new UNIX password: ', 'Retype new UNIX password: '\n        channel.send_data(\"new_pw\\n\")\n      when 'passwd: password updated successfully'\n      else\n        raise \"Unexpected stderr #{stderr}\"\n    end\n  end\nend\n\n# ...\n\nexecute(:passwd, interaction_handler: PasswdInteractionHandler.new)\n```\n\n#### Using the `SSHKit::MappingInteractionHandler`\n\nOften, you want to map directly from a short output string returned by the server (either stdout or stderr)\nto a corresponding input string (as in the case above). For this case you can specify\nthe `interaction_handler` option as a hash. This is used to create a `SSHKit::MappingInteractionHandler` which\nprovides similar functionality to the linux [expect](http://expect.sourceforge.net/) library:\n\n```ruby\nexecute(:passwd, interaction_handler: {\n  '(current) UNIX password: ' =\u003e \"old_pw\\n\",\n  /(Enter|Retype) new UNIX password: / =\u003e \"new_pw\\n\"\n})\n```\n\nNote: the key to the hash keys are matched against the server output `data` using the case equals `===` method.\nThis means that regexes and any objects which define `===` can be used as hash keys.\n\nHash keys are matched in order, which allows for default wildcard matches:\n\n```ruby\nexecute(:my_command, interaction_handler: {\n  \"some specific line\\n\" =\u003e \"specific input\\n\",\n  /.*/ =\u003e \"default input\\n\"\n})\n```\n\nYou can also pass a Proc object to map the output line from the server:\n\n```ruby\nexecute(:passwd, interaction_handler: lambda { |server_data|\n  case server_data\n  when '(current) UNIX password: '\n    \"old_pw\\n\"\n  when /(Enter|Retype) new UNIX password: /\n    \"new_pw\\n\"\n  end\n})\n```\n\n`MappingInteractionHandler`s are stateless, so you can assign one to a constant and reuse it:\n\n```ruby\nENTER_PASSWORD = SSHKit::MappingInteractionHandler.new(\n  \"Please Enter Password\\n\" =\u003e \"some_password\\n\"\n)\n\nexecute(:first_command, interaction_handler: ENTER_PASSWORD)\nexecute(:second_command, interaction_handler: ENTER_PASSWORD)\n```\n\n#### Exploratory logging\n\nBy default, the `MappingInteractionHandler` does not log, in case the server output or input contains sensitive\ninformation. However, if you pass a second `log_level` parameter to the constructor, the `MappingInteractionHandler`\nwill log information about what output is being returned by the server, and what input is being sent\nin response. This can be helpful if you don't know exactly what the server is sending back (whitespace, newlines etc).\n\n```ruby\n  # Start with this and run your script\n  execute(:unfamiliar_command, interaction_handler: MappingInteractionHandler.new({}, :info))\n  # INFO log =\u003e Unable to find interaction handler mapping for stdout:\n  #             \"Please type your input:\\r\\n\" so no response was sent\"\n\n  # Add missing mapping:\n  execute(:unfamiliar_command, interaction_handler: MappingInteractionHandler.new(\n    {\"Please type your input:\\r\\n\" =\u003e \"Some input\\n\"},\n    :info\n  ))\n```\n\n#### The `data` parameter\n\nThe `data` parameter of `on_data(command, stream_name, data, channel)` is a string containing the latest data\ndelivered from the backend.\n\nWhen using the `Netssh` backend for commands where a small amount of data is returned (eg prompting for sudo passwords),\n`on_data` will normally be called once per line and `data` will be terminated by a newline. For commands with\nlarger amounts of output, `data` is delivered as it arrives from the underlying network stack, which depends on\nnetwork conditions, buffer sizes, etc. In this case, you may need to implement a more complex `interaction_handler`\nto concatenate `data` from multiple calls to `on_data` before matching the required output.\n\nWhen using the `Local` backend, `on_data` is always called once per line.\n\n#### The `channel` parameter\n\nWhen using the `Netssh` backend, the `channel` parameter of `on_data(command, stream_name, data, channel)` is a\n[Net::SSH Channel](http://net-ssh.github.io/ssh/v2/api/classes/Net/SSH/Connection/Channel.html).\nWhen using the `Local` backend, it is a [ruby IO](http://ruby-doc.org/core/IO.html) object.\nIf you need to support both sorts of backends with the same interaction handler,\nyou need to call methods on the appropriate API depending on the channel type.\nOne approach is to detect the presence of the API methods you need -\neg `channel.respond_to?(:send_data) # Net::SSH channel` and `channel.respond_to?(:write) # IO`.\nSee the `SSHKit::MappingInteractionHandler` for an example of this.\n\n## Output Handling\n\n![Example Output](https://raw.github.com/leehambley/sshkit/master/examples/images/example_output.png)\n\nBy default, the output format is set to `:pretty`:\n\n```ruby\nSSHKit.config.use_format :pretty\n```\n\nHowever, if you prefer non colored text you can use the `:simpletext` formatter. If you want minimal output,\nthere is also a `:dot` formatter which will simply output red or green dots based on the success or failure of commands.\nThere is also a `:blackhole` formatter which does not output anything.\n\nBy default, formatters log to `$stdout`, but they can be constructed with any object which implements `\u003c\u003c`\nfor example any `IO` subclass, `String`, `Logger` etc:\n\n```ruby\n# Output to a String:\noutput = String.new\nSSHKit.config.output = SSHKit::Formatter::Pretty.new(output)\n# Do something with output\n\n# Or output to a file:\nSSHKit.config.output = SSHKit::Formatter::SimpleText.new(File.open('log/deploy.log', 'wb'))\n```\n\n#### Output \u0026 Log Redaction\n\nIf necessary, `redact` can be used on a section of your `execute` arguments to hide it from both STDOUT and the capistrano.log. It supports the majority of data types.\n\n```ruby\n# Example from capistrano-postgresql gem\nexecute(:psql, fetch(:pg_system_db), '-c', %Q{\"CREATE USER \\\\\"#{fetch(:pg_username)}\\\\\" PASSWORD}, redact(\"'#{fetch(:pg_password)}'\"), %Q{;\"})\n```\nOnce wrapped, sshkit logging will replace the actual pg_password with a [REDACTED] value. The created database user will have the value from `fetch(:pg_password)`.\n\n```\n# STDOUT\n00:00 postgresql:create_database_user\n      01 sudo -i -u postgres psql -d postgres -c \"CREATE USER \\\"db_admin_user\\\" PASSWORD [REDACTED] ;\"\n      01 CREATE ROLE\n    ✔ 01 user@localhost 0.099s\n\n# capistrano.log\nINFO [59dbd2ba] Running /usr/bin/env sudo -i -u postgres psql -d postgres -c \"CREATE USER \\\"db_admin_user\\\" PASSWORD [REDACTED] ;\" as user@localhost\nDEBUG [59dbd2ba] Command: ( export PATH=\"$HOME/.gem/ruby/2.5.0/bin:$PATH\" ; /usr/bin/env sudo -i -u postgres psql -d postgres -c \"CREATE USER \\\"db_admin_user\\\" PASSWORD [REDACTED] ;\" )\nDEBUG [529b623c] CREATE ROLE\n\n```\n\nCertain commands will require that no spaces exist between a string and what you want hidden. Because SSHKIT will include a whitespace between each argument of `execute`, this can be dealt with by wrapping both in redact:\n\n```ruby\n# lib/capistrano/tasks/systemd.rake\nexecute :sudo, :echo, redact(\"CONTENT_WEB_TOOLS_PASS='#{ENV['CONTENT_WEB_TOOLS_PASS']}'\"), \"\u003e\u003e /etc/systemd/system/#{fetch(:application)}_sidekiq.service.d/EnvironmentFile\", '\"'\n\n```\n\n#### Output Colors\n\nBy default, SSHKit will color the output using ANSI color escape sequences\nif the output you are using is associated with a terminal device (tty).\nThis means that you should see colors if you are writing output to the terminal (the default),\nbut you shouldn't see ANSI color escape sequences if you are writing to a file.\n\nColors are supported for the `Pretty` and `Dot` formatters, but for historical reasons\nthe `SimpleText` formatter never shows colors.\n\nIf you want to force SSHKit to show colors, you can set the `SSHKIT_COLOR` environment variable:\n\n```ruby\nENV['SSHKIT_COLOR'] = 'TRUE'\n```\n\n#### Custom formatters\n\nWant custom output formatting? Here's what you have to do:\n\n1. Write a new formatter class in the `SSHKit::Formatter` module. Your class should subclass `SSHKit::Formatter::Abstract` to inherit conveniences and common behavior. For a basic an example, check out the [Pretty](https://github.com/capistrano/sshkit/blob/master/lib/sshkit/formatters/pretty.rb) formatter.\n1. Set the output format as described above. E.g. if your new formatter is called `FooBar`:\n\n```ruby\nSSHKit.config.use_format :foobar\n```\n\nAll formatters that extend from `SSHKit::Formatter::Abstract` accept an options Hash as a constructor argument. You can pass options to your formatter like this:\n\n```ruby\nSSHKit.config.use_format :foobar, :my_option =\u003e \"value\"\n```\n\nYou can then access these options using the `options` accessor within your formatter code.\n\nFor a much more full-featured formatter example that makes use of options, check out the [Airbrussh repository](https://github.com/mattbrictson/airbrussh/).\n\n## Output Verbosity\n\nBy default calls to `capture()` and `test()` are not logged, they are used\n*so* frequently by backend tasks to check environmental settings that it\nproduces a large amount of noise. They are tagged with a verbosity option on\nthe `Command` instances of `Logger::DEBUG`. The default configuration for\noutput verbosity is available to override with `SSHKit.config.output_verbosity=`,\nand defaults to `Logger::INFO`.\nAnother way to is to provide a hash containing `{verbosity: Logger::INFO}` as\na last parameter for the method call.\n\nAt present the `Logger::WARN`, `ERROR` and `FATAL` are not used.\n\n## Deprecation warnings\n\nDeprecation warnings are logged directly to `stderr` by default. This behaviour\ncan be changed by setting the `SSHKit.config.deprecation_output` option:\n\n```ruby\n# Disable deprecation warnings\nSSHKit.config.deprecation_output = nil\n\n# Log deprecation warnings to a file\nSSHKit.config.deprecation_output = File.open('log/deprecation_warnings.log', 'wb')\n```\n\n## Connection Pooling\n\nSSHKit uses a simple connection pool (enabled by default) to reduce the\ncost of negotiating a new SSH connection for every `on()` block. Depending on\nusage and network conditions, this can add up to a significant time savings.\nIn one test, a basic `cap deploy` ran 15-20 seconds faster thanks to the\nconnection pooling added in recent versions of SSHKit.\n\nTo prevent connections from \"going stale\", an existing pooled connection will\nbe replaced with a new connection if it hasn't been used for more than 30\nseconds. This timeout can be changed as follows:\n\n```ruby\nSSHKit::Backend::Netssh.pool.idle_timeout = 60 # seconds\n```\n\nIf you suspect the connection pooling is causing problems, you can disable the\npooling behaviour entirely by setting the idle_timeout to zero:\n\n```ruby\nSSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled\n```\n\n## Tunneling and other related SSH themes\n\nIn order to do special gymnastics with SSH, tunneling, aliasing, complex options, etc with SSHKit it is possible to use [the underlying Net::SSH API](https://github.com/capistrano/sshkit/blob/master/EXAMPLES.md#setting-global-ssh-options) however in many cases it is preferred to use the system SSH configuration file at [`~/.ssh/config`](http://man.cx/ssh_config). This allows you to have personal configuration tied to your machine that does not have to be committed with the repository. If this is not suitable (everyone on the team needs a proxy command, or some special aliasing) a file in the same format can be placed in the project directory at `~/yourproject/.ssh/config`, this will be merged with the system settings in `~/.ssh/config`, and with any configuration specified in [`SSHKit::Backend::Netssh.config.ssh_options`](https://github.com/capistrano/sshkit/blob/master/lib/sshkit/backends/netssh.rb#L133).\n\nThese system level files are the preferred way of setting up tunneling and proxies because the system implementations of these things are faster and better than the Ruby implementations you would get if you were to configure them through Net::SSH. In cases where it's not possible (Windows?), it should be possible to make use of the Net::SSH APIs to setup tunnels and proxy commands before deferring control to Capistrano/SSHKit..\n\n## Proxying\n\nTo connect to the target host via a jump/bastion host, use a `Net::SSH::Proxy::Jump`\n\n```ruby\nhost = SSHKit::Host.new(\n  hostname: 'target.host.com',\n  ssh_options: { proxy: Net::SSH::Proxy::Jump.new(\"proxy.bar.com\") }\n)\non [host] do\n  execute :echo, '1'\nend\n```\n\n## SSHKit Related Blog Posts\n\n[Embedded Capistrano with SSHKit](http://ryandoyle.net/posts/embedded-capistrano-with-sshkit/)\n","funding_links":[],"categories":["Ruby","ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcapistrano%2Fsshkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcapistrano%2Fsshkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcapistrano%2Fsshkit/lists"}