Skip to main content
Glama

ubuntu_website_deployment

Deploy website files to Ubuntu servers and manage backups through SSH connections. Supports deployment, backup creation, and restoration operations.

Instructions

Deploy website files and create backups on Ubuntu

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
connectionIdYesID of an active SSH connection
actionYesAction to perform (deploy, backup, restore)
localPathNoLocal path to the website files for deployment
remotePathNoRemote path where the website is located (default: /var/www/html)
backupPathNoPath to store backups (default: /var/backups/websites)
createBackupNoWhether to create a backup before deployment (default: true)
sudoNoWhether to run the command with sudo (default: true)

Implementation Reference

  • Main handler function for the 'ubuntu_website_deployment' tool. Handles website deployment, backup, and restore operations on Ubuntu servers via SSH. Supports deploying files/directories, creating tar.gz backups, and restoring from backups.
    async ubuntu_website_deployment(params) {
      const { 
        connectionId, 
        action, 
        localPath, 
        remotePath = '/var/www/html', 
        backupPath = '/var/backups/websites',
        createBackup = true,
        sudo = true 
      } = params;
      
      try {
        const conn = getConnection(connectionMap, connectionId);
        const sudoPrefix = sudo ? 'sudo ' : '';
        
        // Validate action
        const validActions = ['deploy', 'backup', 'restore'];
        if (!validActions.includes(action)) {
          throw new Error(`Invalid action: ${action}. Valid actions are: ${validActions.join(', ')}`);
        }
        
        // Create backup directory if it doesn't exist
        await executeSSHCommand(conn, `${sudoPrefix}mkdir -p ${backupPath}`);
        
        // Generate timestamp for backups
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const backupFileName = `website-backup-${timestamp}.tar.gz`;
        const fullBackupPath = `${backupPath}/${backupFileName}`;
        
        let output = '';
        
        if (action === 'deploy') {
          // Check if localPath is provided
          if (!localPath) {
            throw new Error('Local path is required for deployment');
          }
          
          // Create backup before deployment if requested
          if (createBackup) {
            const backupCmd = `${sudoPrefix}tar -czf ${fullBackupPath} -C ${path.dirname(remotePath)} ${path.basename(remotePath)}`;
            const backupResult = await executeSSHCommand(conn, backupCmd);
            output += `Backup created: ${fullBackupPath}\n`;
            
            if (backupResult.code !== 0) {
              output += `Warning: Backup may have issues: ${backupResult.stderr}\n`;
            }
          }
          
          // Expand tilde if present in the local path
          const expandedLocalPath = localPath.replace(/^~/, os.homedir());
          
          // Check if localPath exists
          if (!fs.existsSync(expandedLocalPath)) {
            throw new Error(`Local path does not exist: ${expandedLocalPath}`);
          }
          
          // Get SFTP client for file upload
          const sftp: any = await new Promise((resolve, reject) => {
            conn.sftp((err: Error | undefined, sftp: any) => {
              if (err) {
                reject(new Error(`Failed to initialize SFTP: ${err.message}`));
              } else {
                resolve(sftp);
              }
            });
          });
          
          // Check if localPath is a directory or a single file
          const stats = fs.statSync(expandedLocalPath);
          
          if (stats.isDirectory()) {
            // For directories, we need to zip, upload, and extract
            const tempZipFile = path.join(os.tmpdir(), `deployment-${timestamp}.zip`);
            
            // Create a zip of the directory
            await executeSSHCommand(conn, `zip -r ${tempZipFile} ${expandedLocalPath}`);
            
            // Upload the zip file
            await new Promise((resolve, reject) => {
              sftp.fastPut(tempZipFile, `/tmp/deployment-${timestamp}.zip`, (err: Error | undefined) => {
                if (err) {
                  reject(new Error(`Failed to upload deployment file: ${err.message}`));
                } else {
                  resolve(true);
                }
              });
            });
            
            // Extract the zip file to the destination
            await executeSSHCommand(conn, `${sudoPrefix}unzip -o /tmp/deployment-${timestamp}.zip -d ${remotePath}`);
            
            // Clean up temporary files
            fs.unlinkSync(tempZipFile);
            await executeSSHCommand(conn, `${sudoPrefix}rm /tmp/deployment-${timestamp}.zip`);
            
            output += `Deployed directory ${expandedLocalPath} to ${remotePath}`;
          } else {
            // For a single file, upload directly
            const remoteFilePath = path.join(remotePath, path.basename(expandedLocalPath));
            
            await new Promise((resolve, reject) => {
              sftp.fastPut(expandedLocalPath, remoteFilePath, (err: Error | undefined) => {
                if (err) {
                  reject(new Error(`Failed to upload file: ${err.message}`));
                } else {
                  resolve(true);
                }
              });
            });
            
            // Fix permissions
            await executeSSHCommand(conn, `${sudoPrefix}chown www-data:www-data ${remoteFilePath}`);
            
            output += `Deployed file ${expandedLocalPath} to ${remoteFilePath}`;
          }
        } else if (action === 'backup') {
          // Create backup
          const backupCmd = `${sudoPrefix}tar -czf ${fullBackupPath} -C ${path.dirname(remotePath)} ${path.basename(remotePath)}`;
          const backupResult = await executeSSHCommand(conn, backupCmd);
          
          if (backupResult.code === 0) {
            output += `Backup created: ${fullBackupPath}`;
          } else {
            throw new Error(`Backup failed: ${backupResult.stderr}`);
          }
        } else if (action === 'restore') {
          // List available backups
          const listResult = await executeSSHCommand(conn, `ls -la ${backupPath}`);
          
          // Return list if no specific backup file was provided
          if (!localPath) {
            return {
              content: [{
                type: 'text',
                text: `Available backups:\n\n${listResult.stdout}`
              }]
            };
          }
          
          // Restore from specific backup
          const restoreCmd = `${sudoPrefix}tar -xzf ${localPath} -C ${path.dirname(remotePath)}`;
          const restoreResult = await executeSSHCommand(conn, restoreCmd);
          
          if (restoreResult.code === 0) {
            output += `Restored from backup: ${localPath} to ${remotePath}`;
          } else {
            throw new Error(`Restore failed: ${restoreResult.stderr}`);
          }
        }
        
        return {
          content: [{
            type: 'text',
            text: output
          }]
        };
      } catch (error: any) {
        return {
          content: [{ type: 'text', text: `Website deployment error: ${error.message}` }],
          isError: true
        };
      }
    },
  • Input schema definition for the ubuntu_website_deployment tool, specifying parameters and validation rules.
    ubuntu_website_deployment: {
      description: 'Deploy website files and create backups on Ubuntu',
      inputSchema: {
        type: 'object',
        properties: {
          connectionId: {
            type: 'string',
            description: 'ID of an active SSH connection'
          },
          action: {
            type: 'string',
            description: 'Action to perform (deploy, backup, restore)'
          },
          localPath: {
            type: 'string',
            description: 'Local path to the website files for deployment'
          },
          remotePath: {
            type: 'string',
            description: 'Remote path where the website is located (default: /var/www/html)'
          },
          backupPath: {
            type: 'string',
            description: 'Path to store backups (default: /var/backups/websites)'
          },
          createBackup: {
            type: 'boolean',
            description: 'Whether to create a backup before deployment (default: true)'
          },
          sudo: {
            type: 'boolean',
            description: 'Whether to run the command with sudo (default: true)'
          }
        },
        required: ['connectionId', 'action']
      }
  • src/index.ts:270-299 (registration)
    Registration and dispatch logic in the main CallToolRequestSchema handler. Checks if tool name starts with 'ubuntu_' and invokes the corresponding handler from ubuntuToolHandlers.
    this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
      const toolName = request.params.name;
      
      // Handle core SSH tools directly
      if (toolName.startsWith('ssh_')) {
        switch (toolName) {
          case 'ssh_connect':
            return this.handleSSHConnect(request.params.arguments);
          case 'ssh_exec':
            return this.handleSSHExec(request.params.arguments);
          case 'ssh_upload_file':
            return this.handleSSHUpload(request.params.arguments);
          case 'ssh_download_file':
            return this.handleSSHDownload(request.params.arguments);
          case 'ssh_list_files':
            return this.handleSSHListFiles(request.params.arguments);
          case 'ssh_disconnect':
            return this.handleSSHDisconnect(request.params.arguments);
          default:
            throw new Error(`Unknown SSH tool: ${toolName}`);
        }
      }
      
      // Handle Ubuntu tools directly
      if (toolName.startsWith('ubuntu_') && ubuntuToolHandlers[toolName]) {
        return ubuntuToolHandlers[toolName](request.params.arguments);
      }
      
      throw new Error(`Unknown tool: ${toolName}`);
    });
  • src/index.ts:179-179 (registration)
    Call to addUbuntuTools which overrides ListToolsRequestSchema to include the ubuntu_website_deployment tool in the tool list.
    addUbuntuTools(this.server, this.connections);
  • Utility function used by ubuntu_website_deployment to execute SSH commands with timeout and error handling.
    async function executeSSHCommand(conn: Client, command: string, timeout = 60000): Promise<{
      code: number;
      signal: string;
      stdout: string;
      stderr: string;
    }> {
      return new Promise((resolve, reject) => {
        // Set up timeout
        const timeoutId = setTimeout(() => {
          reject(new Error(`Command execution timed out after ${timeout}ms`));
        }, timeout);
        
        conn.exec(command, {}, (err: Error | undefined, stream: any) => {
          if (err) {
            clearTimeout(timeoutId);
            return reject(new Error(`Failed to execute command: ${err.message}`));
          }
          
          let stdout = '';
          let stderr = '';
          
          stream.on('close', (code: number, signal: string) => {
            clearTimeout(timeoutId);
            resolve({
              code,
              signal,
              stdout: stdout.trim(),
              stderr: stderr.trim()
            });
          });
          
          stream.on('data', (data: Buffer) => {
            stdout += data.toString();
          });
          
          stream.stderr.on('data', (data: Buffer) => {
            stderr += data.toString();
          });
        });
      });
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions 'deploy' and 'create backups' but doesn't specify critical details like whether this is a destructive operation (e.g., overwriting files), permission requirements, error handling, or backup retention. This leaves significant gaps for safe usage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core functionality ('Deploy website files and create backups on Ubuntu') with zero wasted words. It's appropriately sized for the tool's complexity and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 7 parameters, no annotations, and no output schema, the description is incomplete. It doesn't address behavioral aspects like safety (e.g., whether deployment overwrites existing files), error conditions, or what the tool returns. Given the complexity and lack of structured data, more context is needed for reliable use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 100% description coverage, so parameters are well-documented in the schema itself. The description adds minimal value beyond implying the tool handles website files and backups, but it doesn't provide additional context like file format expectations or backup naming conventions. Baseline 3 is appropriate given the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('deploy website files' and 'create backups') and resource ('on Ubuntu'), making it easy to understand what the tool does. However, it doesn't explicitly differentiate from sibling tools like 'ssh_upload_file' or 'ubuntu_nginx_control', which might handle similar operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'ssh_upload_file' for file transfers or 'ubuntu_nginx_control' for web server management. It mentions the action parameter includes 'deploy, backup, restore' but doesn't explain when each is appropriate or any prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mixelpixx/SSH-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server