UNPKG

balena-cli

Version:

The official balena Command Line Interface

193 lines • 7.15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDeviceOsRelease = exports.findBestUsernameForDevice = exports.isRootUserGood = exports.stdioIgnore = exports.RemoteCommandError = exports.SshPermissionDeniedError = void 0; exports.sshArgsForRemoteCommand = sshArgsForRemoteCommand; exports.runRemoteCommand = runRemoteCommand; exports.getRemoteCommandOutput = getRemoteCommandOutput; exports.getLocalDeviceCmdStdout = getLocalDeviceCmdStdout; const child_process_1 = require("child_process"); const _ = require("lodash"); const errors_1 = require("../errors"); class SshPermissionDeniedError extends errors_1.ExpectedError { } exports.SshPermissionDeniedError = SshPermissionDeniedError; class RemoteCommandError extends errors_1.ExpectedError { constructor(cmd, exitCode, exitSignal) { super(sshErrorMessage(cmd, exitSignal, exitCode)); this.cmd = cmd; this.exitCode = exitCode; this.exitSignal = exitSignal; } } exports.RemoteCommandError = RemoteCommandError; exports.stdioIgnore = { stdin: 'ignore', stdout: 'ignore', stderr: 'ignore', }; function sshArgsForRemoteCommand({ cmd = '', hostname, ignoreStdin = false, port, proxyCommand, username = 'root', verbose = false, }) { port = port === 'local' ? 22222 : port === 'cloud' ? 22 : port; return [ ...(verbose ? ['-vvv'] : []), ...(ignoreStdin ? ['-n'] : []), '-t', ...(port ? ['-p', port.toString()] : []), ...['-o', 'LogLevel=ERROR'], ...['-o', 'StrictHostKeyChecking=no'], ...['-o', 'UserKnownHostsFile=/dev/null'], ...(proxyCommand && proxyCommand.length ? ['-o', `ProxyCommand=${proxyCommand.join(' ')}`] : []), `${username}@${hostname}`, ...(cmd ? [cmd] : []), ]; } async function runRemoteCommand({ cmd = '', hostname, port, proxyCommand, stdin = 'inherit', stdout = 'inherit', stderr = 'inherit', username = 'root', verbose = false, }) { let ignoreStdin; if (stdin === 'ignore') { ignoreStdin = true; stdin = 'inherit'; } else { ignoreStdin = false; } const { which } = await Promise.resolve().then(() => require('./which')); const program = await which('ssh'); const args = sshArgsForRemoteCommand({ cmd, hostname, ignoreStdin, port, proxyCommand, username, verbose, }); if (process.env.DEBUG) { const logger = (await Promise.resolve().then(() => require('./logger'))).getLogger(); logger.logDebug(`Executing [${program},${args}]`); } const stdio = [ typeof stdin === 'string' ? stdin : 'pipe', typeof stdout === 'string' ? stdout : 'pipe', typeof stderr === 'string' ? stderr : 'pipe', ]; let exitCode; let exitSignal; try { [exitCode, exitSignal] = await new Promise((resolve, reject) => { const ps = (0, child_process_1.spawn)(program, args, { stdio }) .on('error', reject) .on('close', (code, signal) => resolve([code !== null && code !== void 0 ? code : undefined, signal !== null && signal !== void 0 ? signal : undefined])); if (ps.stdin && stdin && typeof stdin !== 'string') { stdin.pipe(ps.stdin); } if (ps.stdout && stdout && typeof stdout !== 'string') { ps.stdout.pipe(stdout); } if (ps.stderr && stderr && typeof stderr !== 'string') { ps.stderr.pipe(stderr); } }); } catch (error) { const msg = [ `ssh failed with exit code=${exitCode} signal=${exitSignal}:`, `[${program}, ${args.join(', ')}]`, ...(error ? [`${error}`] : []), ]; throw new errors_1.ExpectedError(msg.join('\n')); } if (exitCode || exitSignal) { throw new RemoteCommandError(cmd, exitCode, exitSignal); } } async function getRemoteCommandOutput({ cmd, hostname, port, proxyCommand, stdin = 'ignore', stdout = 'capture', stderr = 'capture', username = 'root', verbose = false, }) { const { Writable } = await Promise.resolve().then(() => require('stream')); const stdoutChunks = []; const stderrChunks = []; const stdoutStream = new Writable({ write(chunk, _enc, callback) { stdoutChunks.push(chunk); callback(); }, }); const stderrStream = new Writable({ write(chunk, _enc, callback) { stderrChunks.push(chunk); callback(); }, }); await runRemoteCommand({ cmd, hostname, port, proxyCommand, stdin, stdout: stdout === 'capture' ? stdoutStream : stdout, stderr: stderr === 'capture' ? stderrStream : stderr, username, verbose, }); return { stdout: Buffer.concat(stdoutChunks), stderr: Buffer.concat(stderrChunks), }; } async function getLocalDeviceCmdStdout(hostname, cmd, stdout = 'capture') { const port = 'local'; return (await getRemoteCommandOutput({ cmd, hostname, port, stdout, stderr: 'inherit', username: await (0, exports.findBestUsernameForDevice)(hostname, port), })).stdout; } exports.isRootUserGood = _.memoize(async (hostname, port) => { try { await runRemoteCommand({ cmd: 'exit 0', hostname, port, ...exports.stdioIgnore }); } catch (e) { return false; } return true; }); exports.findBestUsernameForDevice = _.memoize(async (hostname, port) => { var _a; let username; if (await (0, exports.isRootUserGood)(hostname, port)) { username = 'root'; } else { const { getCachedUsername } = await Promise.resolve().then(() => require('./bootstrap')); username = (_a = (await getCachedUsername())) === null || _a === void 0 ? void 0 : _a.username; } if (!username) { const { stripIndent } = await Promise.resolve().then(() => require('./lazy')); throw new errors_1.ExpectedError(stripIndent ` SSH authentication failed for 'root@${hostname}'. Please login with 'balena login' for alternative authentication.`); } return username; }); exports.getDeviceOsRelease = _.memoize(async (hostname) => (await getLocalDeviceCmdStdout(hostname, 'cat /etc/os-release')).toString()); function sshErrorMessage(cmd, exitSignal, exitCode) { const msg = []; cmd = cmd ? `Remote command "${cmd}"` : 'Process'; if (exitSignal) { msg.push(`SSH: ${cmd} terminated with signal "${exitSignal}"`); } else { msg.push(`SSH: ${cmd} exited with non-zero status code "${exitCode}"`); switch (exitCode) { case 255: msg.push(` Are the SSH keys correctly configured in balenaCloud? See: https://www.balena.io/docs/learn/manage/ssh-access/#add-an-ssh-key-to-balenacloud`); msg.push('Are you accidentally using `sudo`?'); } } return msg.join('\n'); } //# sourceMappingURL=ssh.js.map