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

453 lines 19.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentSSH = 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 AgentSSH { constructor() { this.description = { displayName: 'Agent SSH', name: 'agentSSH', icon: 'fa:robot', iconColor: 'blue', group: ['agent', 'transform'], version: 1, subtitle: '={{$parameter["action"]}}', description: 'SSH tasks for AI agents', defaults: { name: 'Agent SSH', color: '#3366ff', }, inputs: ["main"], outputs: ["main"], properties: [ { displayName: 'Action', name: 'action', type: 'options', options: [ { name: 'Execute Command', value: 'executeCommand', description: 'Execute a command on a remote server', action: 'Execute a command on a remote server', }, { name: 'Download File', value: 'downloadFile', description: 'Download a file from a remote server', action: 'Download a file from a remote server', }, { name: 'Upload File', value: 'uploadFile', description: 'Upload a file to a remote server', action: 'Upload a file to a remote server', }, ], default: 'executeCommand', noDataExpression: true, required: true, description: 'The operation to perform', }, { displayName: 'Authentication Type', name: 'authenticationType', type: 'options', options: [ { name: 'Password', value: 'password', }, { name: 'Private Key', value: 'privateKey', }, ], default: 'password', description: 'Method of authentication', }, { displayName: 'Host', name: 'host', type: 'string', default: '', placeholder: 'hostname.example.com', required: true, description: 'Hostname or IP address of the SSH server', }, { displayName: 'Port', name: 'port', type: 'number', default: 22, description: 'Port number of the SSH server', }, { displayName: 'Username', name: 'username', type: 'string', default: '', required: true, description: 'Username to use for authentication', }, { displayName: 'Password', name: 'password', type: 'string', default: '', required: true, typeOptions: { password: true, }, displayOptions: { show: { authenticationType: ['password'], }, }, description: 'Password to use for authentication', }, { displayName: 'Private Key', name: 'privateKey', type: 'string', default: '', required: true, typeOptions: { rows: 4, password: true, }, displayOptions: { show: { authenticationType: ['privateKey'], }, }, description: 'Private key to use for authentication', }, { displayName: 'Passphrase', name: 'passphrase', type: 'string', default: '', typeOptions: { password: true, }, displayOptions: { show: { authenticationType: ['privateKey'], }, }, description: 'Passphrase for the private key, if required', }, { displayName: 'Command', name: 'command', type: 'string', default: '', required: true, displayOptions: { show: { action: ['executeCommand'], }, }, placeholder: 'ls -la', description: 'The command to execute on the remote server', }, { displayName: 'Working Directory', name: 'workingDirectory', type: 'string', default: '~', displayOptions: { show: { action: ['executeCommand'], }, }, description: 'Directory where the command will be executed', }, { displayName: 'Remote File Path', name: 'remotePath', type: 'string', default: '', required: true, displayOptions: { show: { action: ['downloadFile'], }, }, placeholder: '/home/user/file.txt', description: 'Path to the file on the remote server', }, { displayName: 'Binary Property', name: 'binaryPropertyName', type: 'string', default: 'data', required: true, displayOptions: { show: { action: ['downloadFile', 'uploadFile'], }, }, description: 'Name of the binary property to which to write the file data or from which to read it', }, { displayName: 'Binary Input Field', name: 'binaryInputField', type: 'string', default: 'data', required: true, displayOptions: { show: { action: ['uploadFile'], }, }, description: 'The field with binary data to upload', }, { displayName: 'Remote Directory', name: 'remoteDirectory', type: 'string', default: '~', required: true, displayOptions: { show: { action: ['uploadFile'], }, }, placeholder: '/home/user', description: 'Directory on the remote server where the file will be uploaded', }, { displayName: 'Remote Filename', name: 'remoteFilename', type: 'string', default: '', displayOptions: { show: { action: ['uploadFile'], }, }, description: 'Filename to use on the remote server. Leave blank to use the original filename.', }, { displayName: 'Return Results As', name: 'returnFormat', type: 'options', options: [ { name: 'JSON', value: 'json', description: 'Return results as JSON with detailed information', }, { name: 'Simple Text (stdout)', value: 'simple', description: 'Return only the output text (for command execution)', }, ], default: 'json', description: 'The format in which to return the data', }, { displayName: 'Include Execution Details', name: 'includeDetails', type: 'boolean', default: true, description: 'Whether to include details like exit code and error messages', }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; for (let i = 0; i < items.length; i++) { try { const action = this.getNodeParameter('action', i); const authenticationType = this.getNodeParameter('authenticationType', i); const host = this.getNodeParameter('host', i); const port = this.getNodeParameter('port', i); const username = this.getNodeParameter('username', i); const returnFormat = this.getNodeParameter('returnFormat', i); const includeDetails = this.getNodeParameter('includeDetails', i); const ssh = new node_ssh_1.NodeSSH(); let connectionOptions; if (authenticationType === 'password') { const password = this.getNodeParameter('password', i); connectionOptions = { host, port, username, password, }; } else { const privateKey = this.getNodeParameter('privateKey', i); const passphrase = this.getNodeParameter('passphrase', i, ''); connectionOptions = { host, port, username, privateKey: (0, utilities_1.formatPrivateKey)(privateKey), }; if (passphrase) { connectionOptions.passphrase = passphrase; } } try { await ssh.connect(connectionOptions); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `SSH connection failed: ${error.message}`, { itemIndex: i, }); } let result = {}; try { if (action === 'executeCommand') { const command = this.getNodeParameter('command', i); const workingDirectory = this.getNodeParameter('workingDirectory', i); const cwd = await resolveHomeDir.call(this, workingDirectory, ssh, i); const commandResult = await ssh.execCommand(command, { cwd }); if (returnFormat === 'simple') { result = { output: commandResult.stdout }; } else { result = { stdout: commandResult.stdout, stderr: commandResult.stderr, code: commandResult.code, success: commandResult.code === 0, command, workingDirectory: cwd, }; } } else if (action === 'downloadFile') { const remotePath = this.getNodeParameter('remotePath', i); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i); const resolvedRemotePath = await resolveHomeDir.call(this, remotePath, ssh, i); const binaryFile = await (0, tmp_promise_1.file)({ prefix: 'n8n-agent-ssh-' }); try { await ssh.getFile(binaryFile.path, resolvedRemotePath); const newItem = { json: items[i].json, binary: {}, pairedItem: { item: i, }, }; if (items[i].binary !== undefined && newItem.binary) { Object.assign(newItem.binary, items[i].binary); } newItem.binary[binaryPropertyName] = await this.nodeHelpers.copyBinaryFile(binaryFile.path, resolvedRemotePath.split('/').pop() || 'file'); if (includeDetails || returnFormat === 'json') { newItem.json = { ...newItem.json, downloadResult: { success: true, remotePath: resolvedRemotePath, fileSize: newItem.binary[binaryPropertyName].size, mimeType: newItem.binary[binaryPropertyName].mimeType, fileName: newItem.binary[binaryPropertyName].fileName, }, }; } returnData.push(newItem); continue; } finally { await binaryFile.cleanup(); } } else if (action === 'uploadFile') { const remoteDirectory = this.getNodeParameter('remoteDirectory', i); const remoteFilename = this.getNodeParameter('remoteFilename', i, ''); const binaryInputField = this.getNodeParameter('binaryInputField', i); const binaryData = this.helpers.assertBinaryData(i, binaryInputField); 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-agent-ssh-' }); try { await (0, promises_1.writeFile)(binaryFile.path, uploadData); const resolvedRemoteDir = await resolveHomeDir.call(this, remoteDirectory, ssh, i); const finalRemoteFilename = remoteFilename || binaryData.fileName || 'upload'; const remotePath = `${resolvedRemoteDir}${resolvedRemoteDir.charAt(resolvedRemoteDir.length - 1) === '/' ? '' : '/'}${finalRemoteFilename}`; await ssh.putFile(binaryFile.path, remotePath); result = { success: true, action: 'upload', remotePath, fileName: finalRemoteFilename, fileSize: binaryData.fileSize, }; } finally { await binaryFile.cleanup(); } } if (includeDetails && returnFormat === 'json') { result.connection = { host, port, username, authenticationType, }; } returnData.push({ json: result, pairedItem: { item: i, }, }); } finally { ssh.dispose(); } } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message, success: false, }, pairedItem: { item: i, }, }); continue; } throw error; } } return [returnData]; } } exports.AgentSSH = AgentSSH; //# sourceMappingURL=AgentSSH.node.js.map