It / app.py
Trigger82's picture
Create app.py
8dab15d verified
import os
import subprocess
import shlex
import gradio as gr
import shutil
import time
import threading
import re
# Configuration
REPO_DIR = "levanter"
DEFAULT_REPO = "https://github.com/priscy82/levanter.git"
PM2_APP_NAME = "whatsapp-bot"
def run_command(command, cwd=None, timeout=300):
"""Execute a command and capture output with enhanced environment"""
try:
env = os.environ.copy()
# Set critical environment variables
env['PATH'] = '/usr/local/bin:/usr/bin:/bin'
env['PM2_HOME'] = '/tmp/.pm2'
env['NPM_CONFIG_CACHE'] = '/tmp/npm_cache'
env['YARN_CACHE_FOLDER'] = '/tmp/yarn_cache'
env['NODE_OPTIONS'] = '--max-old-space-size=512'
# Use shlex to properly handle command parsing
process = subprocess.run(
shlex.split(command),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=cwd,
timeout=timeout,
env=env
)
output = process.stdout
if process.stderr:
output += "\nERROR: " + process.stderr
return output
except subprocess.TimeoutExpired:
return "Command timed out after {} seconds".format(timeout)
except Exception as e:
return f"Command failed: {str(e)}"
def clone_and_install(repo_url):
"""Clone repository and install dependencies with robust error handling"""
# Remove existing repo
if os.path.exists(REPO_DIR):
try:
shutil.rmtree(REPO_DIR)
except Exception as e:
return f"❌ Failed to remove existing directory: {str(e)}"
# Clone repo with retries
clone_attempts = 0
clone_output = ""
while clone_attempts < 3:
clone_cmd = f"git clone {repo_url} {REPO_DIR}"
clone_output = run_command(clone_cmd)
if os.path.exists(REPO_DIR):
break
clone_attempts += 1
time.sleep(2)
if not os.path.exists(REPO_DIR):
return f"❌ Clone failed after 3 attempts!\n{clone_output}"
# Navigate to repo and install dependencies
os.chdir(REPO_DIR)
# Check for lock files to determine package manager
package_manager = "yarn" if os.path.exists("yarn.lock") else "npm"
# Install dependencies
install_output = ""
if package_manager == "yarn":
install_output += run_command("yarn install --frozen-lockfile --network-timeout 300000")
else:
install_output += run_command("npm install --legacy-peer-deps --no-audit")
# Handle common errors automatically
if "ERR_PNPM" in install_output or "ERR!" in install_output:
install_output += "\n⚠️ Detected installation issues. Trying fallback..."
install_output += run_command("rm -rf node_modules")
install_output += run_command("rm -f package-lock.json yarn.lock")
install_output += run_command("npm cache clean --force")
install_output += run_command("npm install --legacy-peer-deps --force")
os.chdir("..")
return f"βœ… Repository cloned!\n{clone_output}\n\nπŸ’Ώ Dependencies installed ({package_manager})!\n{install_output}"
def start_bot():
"""Start the bot using the appropriate package manager with PM2"""
if not os.path.exists(REPO_DIR):
return "❌ Repository not found! Clone first."
os.chdir(REPO_DIR)
# Determine package manager
package_manager = "yarn" if os.path.exists("yarn.lock") else "npm"
# Start the bot with PM2
if package_manager == "yarn":
start_cmd = f"pm2 start yarn --name {PM2_APP_NAME} -- start --watch --restart-delay=5000 --max-memory-restart 300M"
else:
start_cmd = f"pm2 start npm --name {PM2_APP_NAME} -- start --watch --restart-delay=5000 --max-memory-restart 300M"
start_output = run_command(start_cmd, timeout=60)
# Save PM2 process list
run_command("pm2 save")
os.chdir("..")
return f"πŸ€– Bot started with PM2!\n{start_output}"
def stop_bot():
"""Stop the bot"""
if not os.path.exists(REPO_DIR):
return "❌ No active bot process"
os.chdir(REPO_DIR)
stop_output = run_command(f"pm2 stop {PM2_APP_NAME}")
os.chdir("..")
return f"πŸ›‘ Bot stopped!\n{stop_output}"
def get_logs():
"""Get bot logs"""
if not os.path.exists(REPO_DIR):
return "No logs available"
os.chdir(REPO_DIR)
logs = run_command(f"pm2 logs {PM2_APP_NAME} --nostream --lines 100")
os.chdir("..")
return logs or "No logs available"
def get_bot_status():
"""Get bot status with detailed information"""
if not os.path.exists(REPO_DIR):
return "Not deployed"
os.chdir(REPO_DIR)
status = run_command(f"pm2 status {PM2_APP_NAME}")
os.chdir("..")
# Enhance status output
if status:
if "online" in status:
return f"🟒 Bot is running!\n{status}"
elif "stopped" in status or "errored" in status:
return f"πŸ”΄ Bot is stopped!\n{status}"
elif "restarting" in status:
return f"🟑 Bot is restarting!\n{status}"
return "Status unknown"
def run_ssh_command(command):
"""Execute custom command in repo directory"""
if not os.path.exists(REPO_DIR):
return "❌ Repository not found! Clone first."
os.chdir(REPO_DIR)
output = run_command(command)
os.chdir("..")
return output
def deploy_sequence(repo_url):
"""Full deployment sequence with error recovery"""
output = clone_and_install(repo_url)
if "❌" in output:
return output
output += "\n\n" + start_bot()
return output
def monitor_bot():
"""Background thread to monitor and restart bot"""
while True:
try:
if os.path.exists(REPO_DIR):
status = get_bot_status()
if "πŸ”΄" in status or "errored" in status:
print("Bot crashed! Restarting...")
start_bot()
except Exception as e:
print(f"Monitor error: {str(e)}")
time.sleep(30)
# Start monitoring thread
monitor_thread = threading.Thread(target=monitor_bot, daemon=True)
monitor_thread.start()
# Gradio Interface
with gr.Blocks(title="WhatsApp Bot Deployer", theme="soft") as demo:
gr.Markdown("""
# πŸ€– WhatsApp Bot Deployment Panel
**Repository:** https://github.com/priscy82/levanter.git
**Node Version:** 23 | **PM2:** Installed | **Auto-Restart:** Enabled
""")
with gr.Tab("πŸš€ Deploy Bot"):
repo_input = gr.Textbox(
label="Git Repository URL",
value=DEFAULT_REPO,
interactive=False
)
deploy_btn = gr.Button("πŸš€ Clone & Deploy", variant="primary")
deploy_output = gr.Textbox(label="Deployment Logs", interactive=False, lines=12)
with gr.Tab("βš™οΈ Bot Controls"):
with gr.Row():
status_btn = gr.Button("πŸ”„ Refresh Status", variant="secondary")
start_btn = gr.Button("▢️ Start Bot", variant="primary")
stop_btn = gr.Button("⏹️ Stop Bot", variant="stop")
restart_btn = gr.Button("πŸ” Restart Bot", variant="primary")
status_output = gr.Textbox(label="Current Status", interactive=False, lines=4)
controls_output = gr.Textbox(label="Action Output", interactive=False, lines=4)
gr.Markdown("### πŸ“‹ Log Viewer")
with gr.Row():
log_lines = gr.Slider(50, 500, value=100, label="Log Lines")
log_btn = gr.Button("πŸ“‹ Get Latest Logs")
log_output = gr.Textbox(label="Bot Logs", interactive=False, lines=18)
with gr.Tab("πŸ’» Terminal"):
gr.Markdown("### ⚑ Run Custom Commands")
cmd_input = gr.Textbox(
label="Enter command",
placeholder="npm install package-name",
lines=1
)
run_btn = gr.Button("▢️ Run", variant="primary")
terminal_output = gr.Textbox(label="Output", interactive=False, lines=12)
gr.Markdown("### πŸš€ Common Commands")
examples = gr.Examples(
examples=[
["npm install --legacy-peer-deps"],
["yarn install --frozen-lockfile"],
["git pull"],
["node --version"],
["npm run build"],
["pm2 status"],
["pm2 logs"]
],
inputs=cmd_input,
label="Click to insert command"
)
# Deployment tab actions
deploy_btn.click(
deploy_sequence,
inputs=repo_input,
outputs=deploy_output
)
# Status and control actions
status_btn.click(
get_bot_status,
outputs=status_output
)
start_btn.click(
start_bot,
outputs=controls_output
).then(
get_bot_status,
outputs=status_output
)
stop_btn.click(
stop_bot,
outputs=controls_output
).then(
get_bot_status,
outputs=status_output
)
restart_btn.click(
lambda: run_ssh_command(f"pm2 restart {PM2_APP_NAME}"),
outputs=controls_output
).then(
get_bot_status,
outputs=status_output
)
# Log actions
log_btn.click(
get_logs,
outputs=log_output
)
# Terminal actions
run_btn.click(
run_ssh_command,
inputs=cmd_input,
outputs=terminal_output
)
if __name__ == "__main__":
demo.launch(server_port=7860, server_name="0.0.0.0")