ssh_deploy
Deploy files to remote servers with automatic permission handling, backup options, and service restart capabilities for secure file transfers.
Instructions
Deploy files to remote server with automatic permission handling
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| server | Yes | Server name or alias | |
| files | Yes | Array of files to deploy | |
| options | No | Deployment options |
Implementation Reference
- src/tool-registry.js:57-72 (registration)Registration of the 'ssh_deploy' tool within the 'advanced' TOOL_GROUPS array. This is where the tool name is listed for conditional registration based on configuration.advanced: [ 'ssh_deploy', 'ssh_execute_sudo', 'ssh_alias', 'ssh_command_alias', 'ssh_hooks', 'ssh_profile', 'ssh_connection_status', 'ssh_tunnel_create', 'ssh_tunnel_list', 'ssh_tunnel_close', 'ssh_key_manage', 'ssh_execute_group', 'ssh_group_manage', 'ssh_history' ]
- src/deploy-helper.js:1-176 (helper)Complete helper module providing functions for ssh_deploy tool: getTempFilename for safe temp files, buildDeploymentStrategy to generate deployment steps (backup, copy, chown, chmod, restart, cleanup), detectDeploymentNeeds for auto-detecting sudo/owner/perms requirements, and createBatchDeployScript for multi-file deployments.import path from 'path'; import crypto from 'crypto'; /** * Deploy helper functions for secure file deployment */ /** * Generate a unique temporary filename */ export function getTempFilename(originalName) { const timestamp = Date.now(); const random = crypto.randomBytes(4).toString('hex'); const ext = path.extname(originalName); const base = path.basename(originalName, ext); return `/tmp/${base}_${timestamp}_${random}${ext}`; } /** * Build deployment strategy based on target path and permissions */ export function buildDeploymentStrategy(remotePath, options = {}) { const { sudoPassword = null, owner = null, permissions = null, backup = true, restart = null } = options; const strategy = { steps: [], requiresSudo: false }; // Step 1: Backup existing file if requested if (backup) { strategy.steps.push({ type: 'backup', command: `if [ -f "${remotePath}" ]; then cp "${remotePath}" "${remotePath}.bak.$(date +%Y%m%d_%H%M%S)"; fi` }); } // Step 2: Determine if we need sudo const needsSudo = remotePath.startsWith('/etc/') || remotePath.startsWith('/var/') || remotePath.startsWith('/usr/') || owner || permissions; if (needsSudo) { strategy.requiresSudo = true; } // Step 3: Copy from temp to final location const copyCmd = needsSudo && sudoPassword ? `echo "${sudoPassword}" | sudo -S cp {{tempFile}} "${remotePath}"` : needsSudo ? `sudo cp {{tempFile}} "${remotePath}"` : `cp {{tempFile}} "${remotePath}"`; strategy.steps.push({ type: 'copy', command: copyCmd }); // Step 4: Set ownership if specified if (owner) { const chownCmd = sudoPassword ? `echo "${sudoPassword}" | sudo -S chown ${owner} "${remotePath}"` : `sudo chown ${owner} "${remotePath}"`; strategy.steps.push({ type: 'chown', command: chownCmd }); } // Step 5: Set permissions if specified if (permissions) { const chmodCmd = sudoPassword ? `echo "${sudoPassword}" | sudo -S chmod ${permissions} "${remotePath}"` : `sudo chmod ${permissions} "${remotePath}"`; strategy.steps.push({ type: 'chmod', command: chmodCmd }); } // Step 6: Restart service if specified if (restart) { strategy.steps.push({ type: 'restart', command: restart }); } // Step 7: Cleanup temp file strategy.steps.push({ type: 'cleanup', command: 'rm -f {{tempFile}}' }); return strategy; } /** * Parse deployment configuration from file path patterns * Examples: * /home/user/app/file.js -> normal deploy * /etc/nginx/sites-available/site -> needs sudo * /var/www/html/index.html -> needs sudo */ export function detectDeploymentNeeds(remotePath) { const needs = { sudo: false, suggestedOwner: null, suggestedPerms: null }; // System directories that typically need sudo if (remotePath.startsWith('/etc/')) { needs.sudo = true; needs.suggestedOwner = 'root:root'; needs.suggestedPerms = '644'; } else if (remotePath.startsWith('/var/www/')) { needs.sudo = true; needs.suggestedOwner = 'www-data:www-data'; needs.suggestedPerms = '644'; } else if (remotePath.includes('/nginx/')) { needs.sudo = true; needs.suggestedOwner = 'root:root'; needs.suggestedPerms = '644'; } else if (remotePath.includes('/apache/') || remotePath.includes('/httpd/')) { needs.sudo = true; needs.suggestedOwner = 'www-data:www-data'; needs.suggestedPerms = '644'; } else if (remotePath.includes('/frappe-bench/')) { // For ERPNext/Frappe deployments needs.sudo = false; needs.suggestedOwner = null; // Will be handled by the app needs.suggestedPerms = '644'; } return needs; } /** * Create batch deployment script for multiple files */ export function createBatchDeployScript(deployments) { const script = ['#!/bin/bash', 'set -e', '']; script.push('# Batch deployment script'); script.push(`# Generated at ${new Date().toISOString()}`); script.push(''); deployments.forEach((deploy, index) => { script.push(`# File ${index + 1}: ${deploy.localPath} -> ${deploy.remotePath}`); deploy.strategy.steps.forEach(step => { if (step.type !== 'cleanup') { script.push(step.command.replace('{{tempFile}}', deploy.tempFile)); } }); script.push(''); }); // Cleanup all temp files at the end script.push('# Cleanup temporary files'); deployments.forEach(deploy => { script.push(`rm -f ${deploy.tempFile}`); }); return script.join('\n'); }