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