#!/usr/bin/env python3 """ Telegram Bot for Cloud Deployment Automation Allows deploying to Fly.io, Render, Railway via Telegram commands """ import sys import subprocess import asyncio from typing import Optional, Tuple # Use shared utilities to reduce code duplication from common.logger_config import setup_basic_logging from common.paths import REPO_ROOT from common.config_loader import get_env_var 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 using shared config logger = setup_basic_logging() # Get allowed user IDs from environment ALLOWED_USER_IDS = get_env_var('TELEGRAM_ALLOWED_USER_IDS', default='').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 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 try: result = subprocess.run( ['flyctl', 'status'], cwd=REPO_ROOT, capture_output=True, text=True, timeout=30 ) if result.returncode == 0: await update.message.reply_text(f"📊 Fly.io Status:\n\n{result.stdout}", parse_mode='HTML') else: await update.message.reply_text(f"❌ Status check failed:\n\n{result.stderr}") except FileNotFoundError: await update.message.reply_text("❌ flyctl not found. Is Fly CLI installed?") except Exception as e: await update.message.reply_text(f"❌ Error checking status: {str(e)}") 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()