#!/usr/bin/env python3 """ Telegram Bot for Cloud Deployment Automation Allows deploying to Fly.io, Render, Railway via Telegram commands """ import os import sys import logging import asyncio import html from pathlib import Path from typing import Optional, Tuple try: from telegram import Update from telegram.ext import Application, CommandHandler, ContextTypes except ImportError: print("python-telegram-bot not installed. Install with: pip install python-telegram-bot") sys.exit(1) # Setup logging logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Repository root (assuming script is in scripts/ directory) REPO_ROOT = Path(__file__).resolve().parents[1] ALLOWED_USER_IDS = os.getenv('TELEGRAM_ALLOWED_USER_IDS', '').split(',') ALLOWED_USER_IDS = [uid.strip() for uid in ALLOWED_USER_IDS if uid.strip()] # Deployment commands mapping DEPLOY_COMMANDS = { 'flyio': ['python', str(REPO_ROOT / 'scripts' / 'deploy_cloud.py'), 'flyio'], 'render': ['python', str(REPO_ROOT / 'scripts' / 'deploy_cloud.py'), 'render'], 'railway': ['python', str(REPO_ROOT / 'scripts' / 'deploy_cloud.py'), 'railway'], 'docker': ['python', str(REPO_ROOT / 'scripts' / 'deploy_cloud.py'), 'docker'], } def check_authorized(user_id: int) -> bool: """Check if user is authorized to use deployment commands""" if not ALLOWED_USER_IDS: logger.warning("No ALLOWED_USER_IDS set - DENYING all users. Set TELEGRAM_ALLOWED_USER_IDS to enable access.") return False return str(user_id) in ALLOWED_USER_IDS async def run_deployment(platform: str) -> Tuple[bool, str]: """Run deployment command and capture output""" if platform not in DEPLOY_COMMANDS: return False, f"Unknown platform: {platform}" cmd = DEPLOY_COMMANDS[platform] try: logger.info(f"Running deployment: {' '.join(cmd)}") process = await asyncio.create_subprocess_exec( *cmd, cwd=REPO_ROOT, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) try: stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=300) output = stdout.decode('utf-8', errors='replace') + stderr.decode('utf-8', errors='replace') if process.returncode == 0: return True, f"✅ Deployment to {platform} successful!\n\n{output[:1000]}" else: return False, f"❌ Deployment to {platform} failed:\n\n{output[:1000]}" except asyncio.TimeoutError: try: process.kill() await process.wait() except Exception: pass return False, f"⏱️ Deployment to {platform} timed out after 5 minutes" except Exception as e: return False, f"❌ Error running deployment: {str(e)}" async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /start command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return welcome_msg = """ 🤖 Deployment Bot Ready! Available commands: /deploy_flyio - Deploy to Fly.io /deploy_render - Deploy to Render.com /deploy_railway - Deploy to Railway.app /deploy_docker - Build Docker image /deploy_exness - Deploy to Exness MT5 /deploy_dashboard - Deploy web dashboard /status - Check deployment status /help - Show this help message Made for MQL5 Trading Automation """ await update.message.reply_text(welcome_msg, parse_mode='HTML') async def deploy_flyio(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /deploy_flyio command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return await update.message.reply_text("🚀 Starting Fly.io deployment...\n⏳ This may take a few minutes...") success, message = await run_deployment('flyio') await update.message.reply_text(message) async def deploy_render(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /deploy_render command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return await update.message.reply_text("🚀 Starting Render deployment...\n⏳ This may take a few minutes...") success, message = await run_deployment('render') await update.message.reply_text(message) async def deploy_railway(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /deploy_railway command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return await update.message.reply_text("🚀 Starting Railway deployment...\n⏳ This may take a few minutes...") success, message = await run_deployment('railway') await update.message.reply_text(message) async def deploy_docker(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /deploy_docker command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return await update.message.reply_text("🐳 Building Docker image...\n⏳ This may take a few minutes...") success, message = await run_deployment('docker') await update.message.reply_text(message) async def check_fly_status() -> str: """Run flyctl status asynchronously""" try: process = await asyncio.create_subprocess_exec( 'flyctl', 'status', cwd=REPO_ROOT, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) try: stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=30) output = stdout.decode('utf-8', errors='replace') + stderr.decode('utf-8', errors='replace') if process.returncode == 0: # Limit output to prevent Telegram message length errors and escape HTML escaped_output = html.escape(output[:3000]) return f"📊 Fly.io Status:\n\n{escaped_output}" else: escaped_output = html.escape(output[:1000]) return f"❌ Status check failed:\n\n{escaped_output}" except asyncio.TimeoutError: try: process.kill() await process.wait() except Exception: pass return "⏱️ Status check timed out after 30 seconds" except FileNotFoundError: return "❌ flyctl not found. Is Fly CLI installed?" except Exception as e: return f"❌ Error checking status: {str(e)}" async def status(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /status command - Check Fly.io app status""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return result_text = await check_fly_status() await update.message.reply_text(result_text, parse_mode='HTML') async def deploy_exness(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /deploy_exness command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return await update.message.reply_text( "📊 Exness Terminal Deployment\n\n" "⚠️ Important: Custom Indicators and EAs are NOT supported on the Exness Web Terminal.\n" "They only work in the Desktop MT5 application.\n\n" "To deploy to Desktop MT5:\n" "1. Run the deployment script on your Windows machine:\n" " powershell -ExecutionPolicy Bypass -File scripts\\deploy_exness.ps1\n\n" "2. Or open web terminal:\n" " https://my.exness.global/webtrading/\n\n" "For detailed instructions, see:\n" "docs/Exness_Deployment_Guide.md", parse_mode='HTML', disable_web_page_preview=True ) async def deploy_dashboard(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /deploy_dashboard command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return platform = context.args[0].lower() if context.args else "all" if platform not in ["flyio", "render", "github", "local", "all"]: await update.message.reply_text( "❌ Invalid platform. Use: flyio, render, github, local, or all" ) return await update.message.reply_text( f"🌐 Deploying Dashboard\n\n" f"Platform: {platform}\n\n" f"Run on your machine:\n" f"powershell -ExecutionPolicy Bypass -File scripts\\deploy_dashboard.ps1 -Platform {platform}\n\n" f"Or for all platforms:\n" f"powershell -ExecutionPolicy Bypass -File scripts\\deploy_dashboard.ps1\n\n" f"📁 Dashboard files: dashboard/", parse_mode='HTML' ) async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /help command""" user_id = update.effective_user.id if not check_authorized(user_id): await update.message.reply_text("❌ You are not authorized to use this bot.") return help_text = """ 📖 Deployment Bot Commands /deploy_flyio - Deploy to Fly.io cloud platform /deploy_render - Deploy to Render.com /deploy_railway - Deploy to Railway.app /deploy_docker - Build Docker image locally /deploy_exness - Deploy to Exness MT5 (Desktop only) /deploy_dashboard [platform] - Deploy web dashboard (flyio/render/github/local/all) /status - Check Fly.io app deployment status /help - Show this help message Note: Deployments may take several minutes. Be patient! """ await update.message.reply_text(help_text, parse_mode='HTML') def main(): """Main function to start the bot""" bot_token = os.getenv('TELEGRAM_BOT_TOKEN') # Try to load from vault if not in environment if not bot_token: try: from load_vault import get_telegram_token bot_token = get_telegram_token() except ImportError: pass if not bot_token: logger.error("TELEGRAM_BOT_TOKEN environment variable not set!") logger.info("Get a token from @BotFather on Telegram") logger.info("Save it in config/vault.json or set: export TELEGRAM_BOT_TOKEN=your_token_here") return logger.info("Starting Telegram Deployment Bot...") # Create application application = Application.builder().token(bot_token).build() # Register command handlers application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("help", help_cmd)) application.add_handler(CommandHandler("deploy_flyio", deploy_flyio)) application.add_handler(CommandHandler("deploy_render", deploy_render)) application.add_handler(CommandHandler("deploy_railway", deploy_railway)) application.add_handler(CommandHandler("deploy_docker", deploy_docker)) application.add_handler(CommandHandler("deploy_exness", deploy_exness)) application.add_handler(CommandHandler("deploy_dashboard", deploy_dashboard)) application.add_handler(CommandHandler("status", status)) # Start the bot logger.info("Bot is running. Press Ctrl+C to stop.") application.run_polling(allowed_updates=Update.ALL_TYPES) if __name__ == '__main__': main()