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
JavaScript
"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