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
| Name | Required | Description | Default |
|---|---|---|---|
| connectionId | Yes | ID of an active SSH connection | |
| action | Yes | Action to perform (deploy, backup, restore) | |
| localPath | No | Local path to the website files for deployment | |
| remotePath | No | Remote path where the website is located (default: /var/www/html) | |
| backupPath | No | Path to store backups (default: /var/backups/websites) | |
| createBackup | No | Whether to create a backup before deployment (default: true) | |
| sudo | No | Whether to run the command with sudo (default: true) |
Implementation Reference
- src/ubuntu-website-tools.ts:256-418 (handler)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 }; } },
- src/ubuntu-website-tools.ts:600-635 (schema)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);
- src/ubuntu-website-tools.ts:18-58 (helper)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(); }); }); }); }