index.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. var ssh2 = require('ssh2')
  2. var fs = require('fs')
  3. var path = require('path')
  4. var duplexify = require('duplexify')
  5. var once = require('once')
  6. var HOME = process.env.HOME || process.env.USERPROFILE
  7. var parse = function (opts) {
  8. if (typeof opts === 'string') {
  9. opts = opts.match(/^(?:([^@]+)@)?([^:]+)(?::(\d+))?$/) || []
  10. opts = {
  11. host: opts[2],
  12. user: opts[1],
  13. port: parseInt(opts[3], 10) || 22
  14. }
  15. }
  16. return opts
  17. }
  18. var exec = function (cmd, opts, cb) {
  19. opts = parse(opts)
  20. var stream = duplexify()
  21. var client = new ssh2.Client()
  22. var key = opts.key === false ? undefined : opts.key || path.join(HOME, '.ssh', 'id_rsa')
  23. var fingerprint
  24. client.on('error', function (err) {
  25. stream.destroy(err)
  26. })
  27. var connect = function () {
  28. if (key && key.toString().toLowerCase().indexOf('encrypted') > -1) key = null
  29. var verifier = function (hash) {
  30. fingerprint = hash
  31. if (!opts.fingerprint) return true
  32. if (fingerprint === opts.fingerprint) return true
  33. client.destroy(new Error('Host could not be verified'))
  34. return false
  35. }
  36. if (opts.password) {
  37. client.on('keyboard-interactive', function (a, b, c, prompt, cb) {
  38. cb([opts.password])
  39. })
  40. }
  41. client.connect({
  42. host: opts.host,
  43. username: opts.user,
  44. password: opts.password,
  45. port: opts.port || 22,
  46. tryKeyboard: !!opts.password,
  47. privateKey: key,
  48. agent: process.env.SSH_AUTH_SOCK,
  49. hostHash: 'md5',
  50. hostVerifier: verifier
  51. })
  52. }
  53. var run = function () {
  54. client.exec(cmd, function (err, stdio) {
  55. if (err) return stream.destroy(err)
  56. stream.setWritable(stdio)
  57. stream.setReadable(stdio)
  58. stream.emit('ready')
  59. stdio.stderr.setEncoding('utf-8')
  60. stdio.stderr.on('data', function (data) {
  61. stream.emit('warn', data)
  62. })
  63. stdio.on('exit', function (code) {
  64. if (code !== 0) {
  65. var err = new Error('Non-zero exit code: ' + code)
  66. err.code = code
  67. stream.emit('error', err)
  68. }
  69. stream.emit('exit', code)
  70. client.end()
  71. })
  72. })
  73. }
  74. var onverify = function (err) {
  75. if (err) return stream.destroy(err)
  76. run()
  77. }
  78. client.once('ready', function () {
  79. if (fingerprint === opts.fingerprint) return run()
  80. if (!stream.emit('verify', fingerprint, onverify)) return run()
  81. })
  82. stream.on('close', function () {
  83. client.end()
  84. })
  85. if (!key || Buffer.isBuffer(key) || key.toString().indexOf('\n') > -1) {
  86. connect()
  87. } else {
  88. fs.readFile(key, function (_, buffer) {
  89. key = buffer
  90. connect()
  91. })
  92. }
  93. if (cb) oncallback(stream, cb)
  94. return stream
  95. }
  96. var oncallback = function (stream, cb) {
  97. cb = once(cb)
  98. var stderr = ''
  99. var stdout = ''
  100. stream.setEncoding('utf-8')
  101. stream.on('warn', function (data) {
  102. stderr += data
  103. })
  104. stream.on('data', function (data) {
  105. stdout += data
  106. })
  107. stream.on('error', function(err) {
  108. cb(err, stdout, stderr)
  109. })
  110. stream.on('end', function () {
  111. cb(null, stdout, stderr)
  112. })
  113. }
  114. module.exports = exec