|
|
import os |
|
|
import subprocess |
|
|
import shlex |
|
|
import gradio as gr |
|
|
import shutil |
|
|
import time |
|
|
import threading |
|
|
import re |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
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' |
|
|
|
|
|
|
|
|
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""" |
|
|
|
|
|
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_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}" |
|
|
|
|
|
|
|
|
os.chdir(REPO_DIR) |
|
|
|
|
|
|
|
|
package_manager = "yarn" if os.path.exists("yarn.lock") else "npm" |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
package_manager = "yarn" if os.path.exists("yarn.lock") else "npm" |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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("..") |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
monitor_thread = threading.Thread(target=monitor_bot, daemon=True) |
|
|
monitor_thread.start() |
|
|
|
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
deploy_btn.click( |
|
|
deploy_sequence, |
|
|
inputs=repo_input, |
|
|
outputs=deploy_output |
|
|
) |
|
|
|
|
|
|
|
|
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_btn.click( |
|
|
get_logs, |
|
|
outputs=log_output |
|
|
) |
|
|
|
|
|
|
|
|
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") |