diff --git a/dist/Swiff.js b/dist/Swiff.js index 33d7cf5..deccef6 100644 --- a/dist/Swiff.js +++ b/dist/Swiff.js @@ -17,8 +17,6 @@ var _username = _interopRequireDefault(require("username")); var _path = _interopRequireDefault(require("path")); -var _ssh = _interopRequireDefault(require("ssh2")); - var _chalk = _interopRequireDefault(require("chalk")); var _utils = require("./utils"); @@ -31,7 +29,7 @@ var _database = require("./database"); var _templates = require("./templates"); -var _ssh2 = require("./ssh"); +var _ssh = require("./ssh"); var _config = require("./config"); @@ -332,10 +330,10 @@ class Swiff extends _react.Component { if (!doesSshKeyExist) return _this.setMessage(`Your${!(0, _utils.isEmpty)(localEnv.SWIFF_CUSTOM_KEY) ? ' custom' : ''} SSH key file wasn’t found at:\n ${(0, _palette.colourNotice)(sshKey)}\n\nYou can either:\n\na) Create a SSH key with this command (leave passphrase empty):\n ${(0, _palette.colourNotice)(`ssh-keygen -m PEM -b 4096 -f ${sshKey}`)}\n\nb) Or add an existing key path in your .env with:\n ${(0, _palette.colourNotice)(`SWIFF_CUSTOM_KEY="/Users/${user}/.ssh/[your-key-name]"`)}${isInteractive ? `\n\nThen hit [ enter ↵ ] to rerun this task` : ''}`); // Check the users SSH key has been added to the server - const checkSshSetup = yield (0, _utils.executeCommands)((0, _ssh2.getSshTestCommand)(config.server.user, config.server.host, config.server.port, !(0, _utils.isEmpty)(localEnv.SWIFF_CUSTOM_KEY) ? localEnv.SWIFF_CUSTOM_KEY : null)); // If there's an issue with the connection then give some assistance + const checkSshSetup = yield (0, _utils.executeCommands)((0, _ssh.getSshTestCommand)(config.server.user, config.server.host, config.server.port, !(0, _utils.isEmpty)(localEnv.SWIFF_CUSTOM_KEY) ? localEnv.SWIFF_CUSTOM_KEY : null)); // If there's an issue with the connection then give some assistance if (checkSshSetup instanceof Error) { - return _this.setMessage(`A SSH connection couldn’t be made with these details:\n\nServer host: ${config.server.host}\nServer user: ${config.server.user}\nPort: ${config.server.port}\nSSH key: ${sshKey}\n\n${(0, _ssh2.getSshCopyInstructions)(config, sshKey)}\n\n${(0, _utils.isEmpty)(localEnv.SWIFF_CUSTOM_KEY) ? `${_chalk.default.bold(`Is the 'SSH key' path above wrong?`)}\nAdd the correct path to your project .env like this:\nSWIFF_CUSTOM_KEY="/Users/${user}/.ssh/id_rsa"` : ''}`); + return _this.setMessage(`A SSH connection couldn’t be made with these details:\n\nServer host: ${config.server.host}\nServer user: ${config.server.user}\nPort: ${config.server.port}\nSSH key: ${sshKey}\n\n${(0, _ssh.getSshCopyInstructions)(config, sshKey)}\n\n${(0, _utils.isEmpty)(localEnv.SWIFF_CUSTOM_KEY) ? `${_chalk.default.bold(`Is the 'SSH key' path above wrong?`)}\nAdd the correct path to your project .env like this:\nSWIFF_CUSTOM_KEY="/Users/${user}/.ssh/id_rsa"` : ''}`); } return true; @@ -366,7 +364,7 @@ class Swiff extends _react.Component { _this.setWorking(`Pulling files from ${(0, _utils.commaAmpersander)(filteredPullFolders)}`); // Create the rsync commands required to pull the files - const pullCommands = (0, _ssh2.getSshPullCommands)({ + const pullCommands = (0, _ssh.getSshPullCommands)({ pullFolders: filteredPullFolders, user: user, host: host, @@ -445,7 +443,7 @@ class Swiff extends _react.Component { _this.setWorking(`Pushing files in ${(0, _utils.commaAmpersander)(filteredPushFolders)}`); // Get the rsync push commands - const pushCommands = (0, _ssh2.getSshPushCommands)({ + const pushCommands = (0, _ssh.getSshPushCommands)({ pushFolders: filteredPushFolders, user: user, host: host, @@ -476,7 +474,8 @@ class Swiff extends _react.Component { DB_PORT, DB_DATABASE, DB_USER, - DB_PASSWORD + DB_PASSWORD, + DB_SOCKET } = localEnv; // Get the remote env file via SSH const remoteEnv = yield (0, _env.getRemoteEnv)({ @@ -494,7 +493,7 @@ class Swiff extends _react.Component { const remoteDbNameZipped = `${remoteDbName}.gz`; const importFile = `${_paths.pathBackups}/${remoteDbName}`; // Download and store the remote DB via SSH - const dbSsh = yield (0, _ssh2.getSshDatabase)({ + const dbSsh = yield (0, _ssh.getSshDatabase)({ remoteEnv: remoteEnv, host: serverConfig.host, user: serverConfig.user, @@ -527,7 +526,8 @@ class Swiff extends _react.Component { port: DB_PORT, user: DB_USER, password: DB_PASSWORD, - database: DB_DATABASE + database: DB_DATABASE, + socketPath: DB_SOCKET }); // If there's any dropping issues then return the messages if (dropTables instanceof Error) return String(dropTables).includes('ER_BAD_DB_ERROR: Unknown database ') ? _this.setMessage(`First create a database named ${(0, _palette.colourNotice)(DB_DATABASE)} on ${(0, _palette.colourNotice)(DB_SERVER)} with these login details:\n\nUsername: ${DB_USER}\nPassword: ${DB_PASSWORD}`) : _this.setError(`There were issues connecting to your local ${(0, _palette.colourAttention)(DB_DATABASE)} database\n\nCheck these settings are correct in your local .env file:\n\n${(0, _palette.colourAttention)(`DB_SERVER="${DB_SERVER}"\nDB_PORT="${DB_PORT}"\nDB_USER="${DB_USER}"\nDB_PASSWORD="${DB_PASSWORD}"\nDB_DATABASE="${DB_DATABASE}"`)}\n\n${(0, _palette.colourMuted)(String(dropTables).replace('Error: ', ''))}`); // Import the remote .sql into the local database @@ -579,7 +579,7 @@ class Swiff extends _react.Component { const remoteDbName = `${remoteEnv.DB_DATABASE}-remote.sql`; const remoteDbNameZipped = `${remoteDbName}.gz`; // Download and store the remote DB via SSH - const dbSsh = yield (0, _ssh2.getSshDatabase)({ + const dbSsh = yield (0, _ssh.getSshDatabase)({ remoteEnv: remoteEnv, host: serverConfig.host, user: serverConfig.user, @@ -609,7 +609,7 @@ class Swiff extends _react.Component { if (localDbDump instanceof Error) return _this.setError(localDbDump); const remoteDbDumpPath = serverConfig.appPath; // Upload local db to remote - const pushDatabase = yield (0, _ssh2.pushSshDatabase)({ + const pushDatabase = yield (0, _ssh.pushSshDatabase)({ host: serverConfig.host, user: serverConfig.user, port: serverConfig.port, @@ -622,7 +622,7 @@ class Swiff extends _react.Component { if (pushDatabase instanceof Error) return _this.setError(pushDatabase); // Create a SSH connection // TODO: Test swiff custom key - const ssh = yield (0, _ssh2.sshConnect)({ + const ssh = yield (0, _ssh.sshConnect)({ host: serverConfig.host, username: serverConfig.user, port: serverConfig.port, @@ -688,7 +688,7 @@ class Swiff extends _react.Component { yield (0, _utils.executeCommands)(`cp composer.json ${_paths.pathBackups}/${DB_DATABASE}-local-composer.json && cp composer.lock ${_paths.pathBackups}/${DB_DATABASE}-local-composer.lock`); // Connect to the remote server - const ssh = yield (0, _ssh2.getSshInit)({ + const ssh = yield (0, _ssh.getSshInit)({ host: serverConfig.host, user: serverConfig.user, sshKeyPath: SWIFF_CUSTOM_KEY @@ -699,7 +699,7 @@ class Swiff extends _react.Component { _this.setWorking(`Fetching the composer files from the remote server at ${(0, _palette.colourHighlight)(serverConfig.host)}`); // Download composer.json from the remote server - const sshDownload1 = yield (0, _ssh2.getSshFile)({ + const sshDownload1 = yield (0, _ssh.getSshFile)({ connection: ssh, from: _path.default.join(serverConfig.appPath, 'composer.json'), to: _path.default.join(_paths.pathApp, 'composer.json') @@ -711,7 +711,7 @@ class Swiff extends _react.Component { } // Download composer.lock from the remote server - const sshDownload2 = yield (0, _ssh2.getSshFile)({ + const sshDownload2 = yield (0, _ssh.getSshFile)({ connection: ssh, from: _path.default.join(serverConfig.appPath, 'composer.lock'), to: _path.default.join(_paths.pathApp, 'composer.lock') @@ -744,19 +744,19 @@ class Swiff extends _react.Component { _this.setWorking(`Backing up the remote composer files on ${(0, _palette.colourHighlight)(serverConfig.host)}`); // Connect to the remote server - const ssh = yield (0, _ssh2.getSshInit)({ + const ssh = yield (0, _ssh.getSshInit)({ host: serverConfig.host, user: serverConfig.user, sshKeyPath: SWIFF_CUSTOM_KEY }); // Download composer.json from the remote server - const sshDownload1 = yield (0, _ssh2.getSshFile)({ + const sshDownload1 = yield (0, _ssh.getSshFile)({ connection: ssh, from: _path.default.join(serverConfig.appPath, 'composer.json'), to: _path.default.join(_paths.pathBackups, `${DB_DATABASE}-remote-composer.json`) }); // Download composer.lock from the remote server - const sshDownload2 = yield (0, _ssh2.getSshFile)({ + const sshDownload2 = yield (0, _ssh.getSshFile)({ connection: ssh, from: _path.default.join(serverConfig.appPath, 'composer.lock'), to: _path.default.join(_paths.pathBackups, `${DB_DATABASE}-remote-composer.lock`) @@ -816,8 +816,13 @@ class Swiff extends _react.Component { // https://github.com/mscdex/ssh2#start-an-interactive-shell-session let gs = null; - const conn = new _ssh.default(); - conn.on('ready', () => { + const ssh = yield (0, _ssh.sshConnect)({ + host: serverConfig.host, + username: serverConfig.user, + port: serverConfig.port, + sshKeyPath: privateKey + }).then(ssh => { + const conn = ssh.connection; conn.shell((err, stream) => { if (err) throw err; // Build the commands to run once we're logged in @@ -837,11 +842,6 @@ class Swiff extends _react.Component { process.exit(1); }); }); - }).connect({ - host: serverConfig.host, - privateKey: require('fs').readFileSync(privateKey), - username: serverConfig.user, - port: serverConfig.port }); // Push our input to the server input // http://stackoverflow.com/questions/5006821/nodejs-how-to-read-keystrokes-from-stdin diff --git a/dist/database.js b/dist/database.js index 7084099..b4829cb 100644 --- a/dist/database.js +++ b/dist/database.js @@ -52,7 +52,8 @@ function () { host: null, user: null, password: null, - database: null // Create the connection to the local database + database: null, + socketPath: null // Create the connection to the local database }; const conn = yield _promiseMysql.default.createConnection(_objectSpread({}, defaultConfig, {}, config)).catch(error => errorMessage = error); diff --git a/dist/ssh.js b/dist/ssh.js index ad85411..120e022 100644 --- a/dist/ssh.js +++ b/dist/ssh.js @@ -23,6 +23,8 @@ var _palette = require("./palette"); var _chalk = _interopRequireDefault(require("chalk")); +var _readlineSync = _interopRequireDefault(require("readline-sync")); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; } @@ -35,6 +37,8 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } +let passphrase; + const getSshInit = /*#__PURE__*/ function () { @@ -107,12 +111,34 @@ function () { const sshKeyResolvedPath = !(0, _utils.isEmpty)(sshKeyPath) ? sshKeyPath : `/Users/${user}/.ssh/id_rsa`; // Create a SSH connection const ssh = new _nodeSsh.default(); - yield ssh.connect({ - host: host, - username: username, - port: port, - privateKey: sshKeyResolvedPath - }).catch(error => errorMessage = error); + + const tryToConnect = + /*#__PURE__*/ + function () { + var _ref4 = _asyncToGenerator(function* () { + errorMessage = null; + yield ssh.connect({ + host: host, + username: username, + port: port, + privateKey: sshKeyResolvedPath, + passphrase: passphrase + }).catch(error => errorMessage = error); + + if (String(errorMessage).includes('Encrypted OpenSSH private key detected, but no passphrase given') || String(errorMessage).includes('Malformed OpenSSH private key. Bad passphrase?')) { + passphrase = _readlineSync.default.question((String(errorMessage).includes('Malformed') ? 'Incorrect passphrase! ' : '') + 'Please enter the private key’s passphrase: ', { + hideEchoBack: true + }); + yield tryToConnect(); + } + }); + + return function tryToConnect() { + return _ref4.apply(this, arguments); + }; + }(); + + yield tryToConnect(); if (errorMessage) return new Error(String(errorMessage).includes('Error: Cannot parse privateKey: Unsupported key format') ? `Your SSH key isn't in a format Swiff can work with\n (${sshKeyResolvedPath})\n\n1. Generate a new one with:\n ${(0, _palette.colourNotice)(`ssh-keygen -m PEM -b 4096 -f /Users/${user}/.ssh/swiff`)}\n\n2. Then add the key to the server:\n ${(0, _palette.colourNotice)(`ssh-copy-id -i /Users/${user}/.ssh/swiff ${port !== 22 ? `-p ${port} ` : ''}${username}@${host}`)}` : String(errorMessage).includes('config.privateKey does not exist at') ? `Your SSH key isn’t found at ${(0, _palette.colourAttention)(sshKeyResolvedPath)}\n\nCheck the ${(0, _palette.colourAttention)(`SWIFF_CUSTOM_KEY`)} value is correct in your local .env\n\nmacOS path example:\n${(0, _palette.colourAttention)(`SWIFF_CUSTOM_KEY="/Users/${user}/.ssh/[key-filename]"`)}` : errorMessage); return ssh; }); @@ -127,7 +153,7 @@ exports.sshConnect = sshConnect; const getSshEnv = /*#__PURE__*/ function () { - var _ref4 = _asyncToGenerator(function* ({ + var _ref5 = _asyncToGenerator(function* ({ host, username, port, @@ -178,7 +204,7 @@ function () { }); return function getSshEnv(_x4) { - return _ref4.apply(this, arguments); + return _ref5.apply(this, arguments); }; }(); @@ -288,14 +314,14 @@ exports.getSshTestCommand = getSshTestCommand; const pushSshDatabase = /*#__PURE__*/ function () { - var _ref5 = _asyncToGenerator(function* (config) { + var _ref6 = _asyncToGenerator(function* (config) { const pushDatabaseStatus = yield (0, _utils.executeCommands)(getPushDatabaseCommands(config)); if (pushDatabaseStatus instanceof Error) return new Error(`There was an issue uploading your local ${(0, _palette.colourAttention)(config.dbName)} database\n\n${pushDatabaseStatus}`); return; }); return function pushSshDatabase(_x5) { - return _ref5.apply(this, arguments); + return _ref6.apply(this, arguments); }; }(); // Download a database over SSH to a local folder @@ -305,7 +331,7 @@ exports.pushSshDatabase = pushSshDatabase; const getSshDatabase = /*#__PURE__*/ function () { - var _ref6 = _asyncToGenerator(function* ({ + var _ref7 = _asyncToGenerator(function* ({ remoteEnv, host, user, @@ -372,7 +398,7 @@ function () { }); return function getSshDatabase(_x6) { - return _ref6.apply(this, arguments); + return _ref7.apply(this, arguments); }; }(); diff --git a/package-lock.json b/package-lock.json index be2525e..18bd7dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1074,6 +1074,12 @@ "csstype": "^2.2.0" } }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "acorn": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", @@ -2426,6 +2432,12 @@ "integrity": "sha512-n5tKOjMaZ1fksIpQbjERuqCyfgec/m9pferkFQbLmWtqLUdmt12hNhjSwsmPdqeiG2NkITOQhr1VYIwWSAceiA==", "dev": true }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -2756,6 +2768,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -5150,6 +5171,12 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -5159,6 +5186,12 @@ "has": "^1.0.1" } }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -5845,6 +5878,287 @@ "ssh2": "^0.8.2" } }, + "nodemon": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.1.tgz", + "integrity": "sha512-UC6FVhNLXjbbV4UzaXA3wUdbEkUZzLGgMGzmxvWAex5nzib/jhcSHVFlQODdbuUHq8SnnZ4/EABBAbC3RplvPg==", + "dev": true, + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -6503,6 +6817,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -6599,6 +6919,11 @@ "readable-stream": "^2.0.2" } }, + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==" + }, "redent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", @@ -7519,6 +7844,12 @@ "integrity": "sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=", "dev": true }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -7585,6 +7916,15 @@ "repeat-string": "^1.6.1" } }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -7665,6 +8005,32 @@ "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=", "dev": true }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -7784,6 +8150,12 @@ } } }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, "upath": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", diff --git a/package.json b/package.json index 7841f27..cefb2a9 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "node-ssh": "^6.0.0", "promise-mysql": "^4.0.4", "react": "^16.8.6", - "ssh2": "^0.8.5", + "readline-sync": "^1.4.10", "universal-analytics": "^0.4.20", "update-notifier": "^3.0.1", "username": "^5.1.0" @@ -68,6 +68,7 @@ "babel-plugin-transform-react-jsx": "^6.24.1", "eslint": "^6.0.1", "eslint-plugin-react": "^7.14.2", + "nodemon": "^2.0.1", "prettier": "^1.18.2" }, "xo": { @@ -84,4 +85,4 @@ "comma-dangle": "off" } } -} \ No newline at end of file +} diff --git a/src/Swiff.js b/src/Swiff.js index bc2de70..b52685b 100644 --- a/src/Swiff.js +++ b/src/Swiff.js @@ -4,7 +4,6 @@ import { exec } from 'child_process' import ua from 'universal-analytics' import resolveUsername from 'username' import path from 'path' -import ssh2 from 'ssh2' import chalk from 'chalk' import { isEmpty, @@ -662,6 +661,7 @@ class Swiff extends Component { DB_DATABASE, DB_USER, DB_PASSWORD, + DB_SOCKET, } = localEnv // Get the remote env file via SSH const remoteEnv = await getRemoteEnv({ @@ -719,6 +719,7 @@ class Swiff extends Component { user: DB_USER, password: DB_PASSWORD, database: DB_DATABASE, + socketPath: DB_SOCKET, }) // If there's any dropping issues then return the messages if (dropTables instanceof Error) @@ -1074,8 +1075,13 @@ class Swiff extends Component { // Create an interactive shell session // https://github.com/mscdex/ssh2#start-an-interactive-shell-session let gs = null - const conn = new ssh2() - conn.on('ready', () => { + const ssh = await sshConnect({ + host: serverConfig.host, + username: serverConfig.user, + port: serverConfig.port, + sshKeyPath: privateKey, + }).then(ssh => { + const conn = ssh.connection conn.shell((err, stream) => { if (err) throw err // Build the commands to run once we're logged in @@ -1108,11 +1114,6 @@ class Swiff extends Component { process.exit(1) }) }) - }).connect({ - host: serverConfig.host, - privateKey: require('fs').readFileSync(privateKey), - username: serverConfig.user, - port: serverConfig.port, }) // Push our input to the server input // http://stackoverflow.com/questions/5006821/nodejs-how-to-read-keystrokes-from-stdin diff --git a/src/database.js b/src/database.js index b9577a3..63c07d4 100644 --- a/src/database.js +++ b/src/database.js @@ -32,6 +32,7 @@ const doDropAllDbTables = async config => { user: null, password: null, database: null, + socketPath: null, } // Create the connection to the local database const conn = await mysql diff --git a/src/ssh.js b/src/ssh.js index 37008a5..b77bbd7 100644 --- a/src/ssh.js +++ b/src/ssh.js @@ -7,6 +7,9 @@ import { getDbDumpZipCommands } from './database' import { pathBackups, pathApp } from './paths' import { colourAttention, colourNotice } from './palette' import chalk from 'chalk' +import readlineSync from 'readline-sync' + +let passphrase const getSshInit = async ({ host, user, port, sshKeyPath }) => { // Connect to the remote server via SSH @@ -46,14 +49,25 @@ const sshConnect = async ({ host, username, port, sshKeyPath }) => { : `/Users/${user}/.ssh/id_rsa` // Create a SSH connection const ssh = new nodeSsh() - await ssh - .connect({ - host: host, - username: username, - port: port, - privateKey: sshKeyResolvedPath, - }) - .catch(error => (errorMessage = error)) + const tryToConnect = async () => { + errorMessage = null + await ssh + .connect({ + host: host, + username: username, + port: port, + privateKey: sshKeyResolvedPath, + passphrase: passphrase, + }) + .catch(error => (errorMessage = error)) + if (String(errorMessage).includes('Encrypted OpenSSH private key detected, but no passphrase given') || String(errorMessage).includes('Malformed OpenSSH private key. Bad passphrase?')) { + passphrase = readlineSync.question((String(errorMessage).includes('Malformed') ? 'Incorrect passphrase! ' : '') + 'Please enter the private key’s passphrase: ', { + hideEchoBack: true, + }) + await tryToConnect() + } + } + await tryToConnect() if (errorMessage) return new Error( String(errorMessage).includes(