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

597 lines 24.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SshTool = 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 SshTool { constructor() { this.description = { displayName: 'Hadidiz-AI', name: 'hadidizAi', icon: 'fa:terminal', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"]}}', description: 'SSH operations that can be used with AI Agents', defaults: { name: 'Hadidiz-AI', color: '#5E83C2', }, usableAsTool: true, codex: { categories: ['Networking', 'System Administration'], subcategories: { Networking: ['SSH', 'Remote Access'], 'System Administration': ['Commands', 'File Transfer'], }, resources: { primaryDocumentation: [ { url: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.ssh/', }, ], }, alias: ['ssh', 'terminal', 'remote', 'command', 'hadidiz'], }, inputs: ["main"], outputs: ["main"], credentials: [ { name: 'sshPassword', required: false, displayOptions: { show: { authentication: ['password'], connectionType: ['credentials'], }, }, }, { name: 'sshPrivateKey', required: false, displayOptions: { show: { authentication: ['privateKey'], connectionType: ['credentials'], }, }, }, ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, 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', }, { 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: 'parameters', }, { 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: 'Command', name: 'command', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['executeCommand'], }, }, description: 'The command to execute on the remote server', placeholder: 'ls -la', }, { displayName: 'Working Directory', name: 'workingDirectory', type: 'string', default: '~', displayOptions: { show: { operation: ['executeCommand'], }, }, description: 'Directory where the command will be executed', }, { displayName: 'Remote File Path', name: 'remotePath', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['downloadFile'], }, }, placeholder: '/home/user/file.txt', description: 'Path to the file on the remote server', }, { displayName: 'Binary Property Name', name: 'binaryPropertyName', type: 'string', default: 'data', displayOptions: { show: { operation: ['downloadFile'], }, }, description: 'Name of the binary property to store the downloaded file', }, { displayName: 'Upload From', name: 'uploadSource', type: 'options', options: [ { name: 'Binary Data', value: 'binaryData', description: 'Use binary field data', }, { name: 'Text Content', value: 'textContent', description: 'Use text content', }, ], default: 'textContent', displayOptions: { show: { operation: ['uploadFile'], }, }, }, { displayName: 'Binary Input Field', name: 'binaryInputField', type: 'string', default: 'data', required: true, displayOptions: { show: { operation: ['uploadFile'], uploadSource: ['binaryData'], }, }, description: 'The field with binary data to upload', }, { displayName: 'File Content', name: 'fileContent', type: 'string', default: '', typeOptions: { rows: 4, }, displayOptions: { show: { operation: ['uploadFile'], uploadSource: ['textContent'], }, }, description: 'The text content to upload as a file', }, { displayName: 'Remote Directory', name: 'remoteDirectory', type: 'string', default: '~', required: true, displayOptions: { show: { operation: ['uploadFile'], }, }, description: 'Directory on the remote server where the file will be uploaded', }, { displayName: 'Remote Filename', name: 'remoteFilename', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['uploadFile'], }, }, description: 'Name of the file on the remote server', }, ], }; this.methods = { loadOptions: {}, }; } getImplementationDescription(operation) { switch (operation) { case 'executeCommand': return 'Executes a command on a remote server via SSH and returns the output'; case 'downloadFile': return 'Downloads a file from a remote server via SSH and returns it as binary data'; case 'uploadFile': return 'Uploads a file to a remote server via SSH'; default: return 'Performs SSH operations on a remote server'; } } getParameterDescription(parameter, operation) { const descriptions = { host: { all: 'The hostname or IP address of the SSH server to connect to', }, port: { all: 'The port number of the SSH server (default: 22)', }, username: { all: 'The username to authenticate with on the SSH server', }, password: { all: 'The password to authenticate with on the SSH server', }, privateKey: { all: 'The private key to authenticate with on the SSH server', }, passphrase: { all: 'The passphrase for the private key if required', }, command: { executeCommand: 'The shell command to execute on the remote server', }, workingDirectory: { executeCommand: 'The directory on the remote server where the command will be executed', }, remotePath: { downloadFile: 'The full path to the file on the remote server that should be downloaded', }, remoteDirectory: { uploadFile: 'The directory on the remote server where the file should be uploaded to', }, remoteFilename: { uploadFile: 'The name of the file on the remote server', }, fileContent: { uploadFile: 'The content of the file to upload to the remote server', }, }; if (parameter in descriptions) { if (operation in descriptions[parameter]) { return descriptions[parameter][operation]; } else if ('all' in descriptions[parameter]) { return descriptions[parameter].all; } } return ''; } async execute() { const returnData = []; const itemIndex = 0; try { const operation = this.getNodeParameter('operation', itemIndex); const connectionType = this.getNodeParameter('connectionType', itemIndex); const authentication = this.getNodeParameter('authentication', itemIndex); const ssh = new node_ssh_1.NodeSSH(); let connectionOptions; if (connectionType === 'credentials') { if (authentication === 'password') { const credentials = await this.getCredentials('sshPassword'); connectionOptions = { host: credentials.host, username: credentials.username, port: credentials.port, password: credentials.password, }; } else { const credentials = await this.getCredentials('sshPrivateKey'); connectionOptions = { host: credentials.host, username: credentials.username, port: credentials.port, privateKey: (0, utilities_1.formatPrivateKey)(credentials.privateKey), }; if (credentials.passphrase) { connectionOptions.passphrase = credentials.passphrase; } } } else { const host = this.getNodeParameter('host', itemIndex); const port = this.getNodeParameter('port', itemIndex); const username = this.getNodeParameter('username', itemIndex); if (authentication === 'password') { const password = this.getNodeParameter('password', itemIndex); connectionOptions = { host, port, username, password, }; } else { const privateKey = this.getNodeParameter('privateKey', itemIndex); const passphrase = this.getNodeParameter('passphrase', itemIndex, ''); 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 }); } try { if (operation === 'executeCommand') { const command = this.getNodeParameter('command', itemIndex); const workingDirectory = this.getNodeParameter('workingDirectory', itemIndex); const cwd = await resolveHomeDir.call(this, workingDirectory, ssh, itemIndex); const result = await ssh.execCommand(command, { cwd }); const output = { stdout: result.stdout, stderr: result.stderr, exitCode: result.code, success: result.code === 0, command, workingDirectory: cwd, }; returnData.push({ json: output, }); } else if (operation === 'downloadFile') { const remotePath = this.getNodeParameter('remotePath', itemIndex); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex); const resolvedRemotePath = await resolveHomeDir.call(this, remotePath, ssh, itemIndex); const binaryFile = await (0, tmp_promise_1.file)({ prefix: 'n8n-ssh-tool-' }); try { await ssh.getFile(binaryFile.path, resolvedRemotePath); const fileName = resolvedRemotePath.split('/').pop() || 'file'; const newItem = { json: { success: true, operation: 'downloadFile', remotePath: resolvedRemotePath, fileName, }, binary: {}, }; newItem.binary[binaryPropertyName] = await this.nodeHelpers.copyBinaryFile(binaryFile.path, fileName); returnData.push(newItem); } finally { await binaryFile.cleanup(); } } else if (operation === 'uploadFile') { const remoteDirectory = this.getNodeParameter('remoteDirectory', itemIndex); const remoteFilename = this.getNodeParameter('remoteFilename', itemIndex); const uploadSource = this.getNodeParameter('uploadSource', itemIndex); const resolvedRemoteDir = await resolveHomeDir.call(this, remoteDirectory, ssh, itemIndex); const remotePath = `${resolvedRemoteDir}${resolvedRemoteDir.charAt(resolvedRemoteDir.length - 1) === '/' ? '' : '/'}${remoteFilename}`; const binaryFile = await (0, tmp_promise_1.file)({ prefix: 'n8n-ssh-tool-' }); try { if (uploadSource === 'binaryData') { const binaryInputField = this.getNodeParameter('binaryInputField', itemIndex); const binaryData = this.helpers.assertBinaryData(itemIndex, binaryInputField); let uploadData; if (binaryData.id) { uploadData = await this.helpers.getBinaryStream(binaryData.id); } else { uploadData = Buffer.from(binaryData.data, n8n_workflow_1.BINARY_ENCODING); } await (0, promises_1.writeFile)(binaryFile.path, uploadData); } else { const fileContent = this.getNodeParameter('fileContent', itemIndex); await (0, promises_1.writeFile)(binaryFile.path, fileContent); } await ssh.putFile(binaryFile.path, remotePath); returnData.push({ json: { success: true, operation: 'uploadFile', remotePath, }, }); } finally { await binaryFile.cleanup(); } } } finally { ssh.dispose(); } return [returnData]; } catch (error) { if (this.continueOnFail()) { return [ [ { json: { success: false, error: error.message, }, }, ], ]; } throw error; } } } exports.SshTool = SshTool; //# sourceMappingURL=SshTool.node.js.map