UNPKG

n8n-nodes-sshv2

Version:

2 N8N ( Node & AI Agent Tool) for SSH operations Dynamically Configurable parameters NO credentials, including command execution, file uploads, and downloads by Hadidiz, HadidizFlow

576 lines 23.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Sshv2 = void 0; const promises_1 = require("fs/promises"); const n8n_workflow_1 = require("n8n-workflow"); const node_ssh_1 = require("node-ssh"); const tmp_promise_1 = require("tmp-promise"); const utilities_1 = require("../../utils/utilities"); async function resolveHomeDir(path, ssh, itemIndex) { if (path.startsWith('~/')) { let homeDir = (await ssh.execCommand('echo $HOME')).stdout; if (homeDir.charAt(homeDir.length - 1) !== '/') { homeDir += '/'; } return path.replace('~/', homeDir); } if (path.startsWith('~')) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid path. Replace "~" with home directory or "~/"', { itemIndex, }); } return path; } class Sshv2 { constructor() { this.description = { displayName: 'SSH V2', name: 'sshv2', icon: 'fa:terminal', iconColor: 'black', group: ['input'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Execute commands via SSH', defaults: { name: 'SSH', color: '#000000', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'sshPassword', required: true, testedBy: 'sshConnectionTest', displayOptions: { show: { connectionType: ['credentials'], authentication: ['password'], }, }, }, { name: 'sshPrivateKey', required: true, testedBy: 'sshConnectionTest', displayOptions: { show: { connectionType: ['credentials'], authentication: ['privateKey'], }, }, }, ], properties: [ { displayName: 'Connection Type', name: 'connectionType', type: 'options', options: [ { name: 'Credentials', value: 'credentials', description: 'Use stored credentials', }, { name: 'Dynamic Parameters', value: 'parameters', description: 'Use dynamic parameters for connection', }, ], default: 'credentials', }, { displayName: 'Authentication', name: 'authentication', type: 'options', options: [ { name: 'Password', value: 'password', }, { name: 'Private Key', value: 'privateKey', }, ], default: 'password', }, { displayName: 'Host', name: 'host', type: 'string', default: '', placeholder: 'localhost', required: true, displayOptions: { show: { connectionType: ['parameters'], }, }, description: 'Hostname or IP address of the SSH server', }, { displayName: 'Port', name: 'port', type: 'number', default: 22, required: true, displayOptions: { show: { connectionType: ['parameters'], }, }, description: 'Port number of the SSH server', }, { displayName: 'Username', name: 'username', type: 'string', default: '', required: true, displayOptions: { show: { connectionType: ['parameters'], }, }, description: 'Username to use for authentication', }, { displayName: 'Password', name: 'password', type: 'string', default: '', required: true, typeOptions: { password: true, }, displayOptions: { show: { connectionType: ['parameters'], authentication: ['password'], }, }, description: 'Password to use for authentication', }, { displayName: 'Private Key', name: 'privateKey', type: 'string', default: '', required: true, typeOptions: { rows: 4, password: true, }, displayOptions: { show: { connectionType: ['parameters'], authentication: ['privateKey'], }, }, description: 'Private key to use for authentication', }, { displayName: 'Passphrase', name: 'passphrase', type: 'string', default: '', typeOptions: { password: true, }, displayOptions: { show: { connectionType: ['parameters'], authentication: ['privateKey'], }, }, description: 'Passphrase for the private key, if required', }, { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Command', value: 'command', }, { name: 'File', value: 'file', }, ], default: 'command', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['command'], }, }, options: [ { name: 'Execute', value: 'execute', description: 'Execute a command', action: 'Execute a command', }, ], default: 'execute', }, { displayName: 'Command', name: 'command', type: 'string', displayOptions: { show: { resource: ['command'], operation: ['execute'], }, }, default: '', description: 'The command to be executed on a remote device', }, { displayName: 'Working Directory', name: 'cwd', type: 'string', displayOptions: { show: { resource: ['command'], operation: ['execute'], }, }, default: '/', required: true, }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['file'], }, }, options: [ { name: 'Download', value: 'download', description: 'Download a file', action: 'Download a file', }, { name: 'Upload', value: 'upload', description: 'Upload a file', action: 'Upload a file', }, ], default: 'upload', }, { displayName: 'Input Binary Field', name: 'binaryPropertyName', type: 'string', default: 'data', required: true, displayOptions: { show: { operation: ['upload'], resource: ['file'], }, }, placeholder: '', hint: 'The name of the input binary field containing the file to be uploaded', }, { displayName: 'Target Directory', name: 'path', type: 'string', displayOptions: { show: { resource: ['file'], operation: ['upload'], }, }, default: '', required: true, placeholder: '/home/user', description: 'The directory to upload the file to. The name of the file does not need to be specified, it\'s taken from the binary data file name. To override this behavior, set the parameter "File Name" under options.', }, { displayName: 'Path', displayOptions: { show: { resource: ['file'], operation: ['download'], }, }, name: 'path', type: 'string', default: '', placeholder: '/home/user/invoice.txt', description: 'The file path of the file to download. Has to contain the full path including file name.', required: true, }, { displayName: 'File Property', displayOptions: { show: { resource: ['file'], operation: ['download'], }, }, name: 'binaryPropertyName', type: 'string', default: 'data', description: 'Object property name which holds binary data', required: true, }, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add option', displayOptions: { show: { resource: ['file'], operation: ['upload', 'download'], }, }, default: {}, options: [ { displayName: 'File Name', name: 'fileName', type: 'string', default: '', description: 'Overrides the binary data file name', }, ], }, ], }; this.methods = { credentialTest: { async sshConnectionTest(credential) { const credentials = credential.data; const ssh = new node_ssh_1.NodeSSH(); try { if (!credentials.privateKey) { await ssh.connect({ host: credentials.host, username: credentials.username, port: credentials.port, password: credentials.password, }); } else { const options = { host: credentials.host, username: credentials.username, port: credentials.port, privateKey: (0, utilities_1.formatPrivateKey)(credentials.privateKey), }; if (credentials.passphrase) { options.passphrase = credentials.passphrase; } await ssh.connect(options); } } catch (error) { const message = `SSH connection failed: ${error.message}`; return { status: 'Error', message, }; } finally { ssh.dispose(); } return { status: 'OK', message: 'Connection successful!', }; }, }, }; } async execute() { const items = this.getInputData(); const returnItems = []; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); const connectionType = this.getNodeParameter('connectionType', 0); const authentication = this.getNodeParameter('authentication', 0); const ssh = new node_ssh_1.NodeSSH(); try { if (connectionType === 'credentials') { if (authentication === 'password') { const credentials = await this.getCredentials('sshPassword'); await ssh.connect({ host: credentials.host, username: credentials.username, port: credentials.port, password: credentials.password, }); } else if (authentication === 'privateKey') { const credentials = await this.getCredentials('sshPrivateKey'); const options = { host: credentials.host, username: credentials.username, port: credentials.port, privateKey: (0, utilities_1.formatPrivateKey)(credentials.privateKey), }; if (credentials.passphrase) { options.passphrase = credentials.passphrase; } await ssh.connect(options); } } else { const host = this.getNodeParameter('host', 0); const port = this.getNodeParameter('port', 0); const username = this.getNodeParameter('username', 0); if (authentication === 'password') { const password = this.getNodeParameter('password', 0); await ssh.connect({ host, port, username, password, }); } else if (authentication === 'privateKey') { const privateKey = this.getNodeParameter('privateKey', 0); const passphrase = this.getNodeParameter('passphrase', 0, ''); const options = { host, port, username, privateKey: (0, utilities_1.formatPrivateKey)(privateKey), }; if (passphrase) { options.passphrase = passphrase; } await ssh.connect(options); } } for (let i = 0; i < items.length; i++) { try { if (resource === 'command') { if (operation === 'execute') { const command = this.getNodeParameter('command', i); const cwd = await resolveHomeDir.call(this, this.getNodeParameter('cwd', i), ssh, i); returnItems.push({ json: (await ssh.execCommand(command, { cwd })), pairedItem: { item: i, }, }); } } if (resource === 'file') { if (operation === 'download') { const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i); const parameterPath = await resolveHomeDir.call(this, this.getNodeParameter('path', i), ssh, i); const binaryFile = await (0, tmp_promise_1.file)({ prefix: 'n8n-ssh-' }); try { await ssh.getFile(binaryFile.path, parameterPath); const newItem = { json: items[i].json, binary: {}, pairedItem: { item: i, }, }; if (items[i].binary !== undefined && newItem.binary) { Object.assign(newItem.binary, items[i].binary); } items[i] = newItem; const fileName = this.getNodeParameter('options.fileName', i, ''); items[i].binary[dataPropertyNameDownload] = await this.nodeHelpers.copyBinaryFile(binaryFile.path, fileName || parameterPath); } finally { await binaryFile.cleanup(); } } if (operation === 'upload') { const parameterPath = await resolveHomeDir.call(this, this.getNodeParameter('path', i), ssh, i); const fileName = this.getNodeParameter('options.fileName', i, ''); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i); const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); let uploadData; if (binaryData.id) { uploadData = await this.helpers.getBinaryStream(binaryData.id); } else { uploadData = Buffer.from(binaryData.data, n8n_workflow_1.BINARY_ENCODING); } const binaryFile = await (0, tmp_promise_1.file)({ prefix: 'n8n-ssh-' }); try { await (0, promises_1.writeFile)(binaryFile.path, uploadData); await ssh.putFile(binaryFile.path, `${parameterPath}${parameterPath.charAt(parameterPath.length - 1) === '/' ? '' : '/'}${fileName || binaryData.fileName}`); returnItems.push({ json: { success: true, }, pairedItem: { item: i, }, }); } finally { await binaryFile.cleanup(); } } } } catch (error) { if (this.continueOnFail()) { if (resource === 'file' && operation === 'download') { items[i] = { json: { error: error.message, }, }; } else { returnItems.push({ json: { error: error.message, }, pairedItem: { item: i, }, }); } continue; } throw error; } } } finally { ssh.dispose(); } if (resource === 'file' && operation === 'download') { return [items]; } else { return [returnItems]; } } } exports.Sshv2 = Sshv2; //# sourceMappingURL=Sshv2.node.js.map