Trigger82 commited on
Commit
8dab15d
Β·
verified Β·
1 Parent(s): 7d4fc93

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +299 -0
app.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import shlex
4
+ import gradio as gr
5
+ import shutil
6
+ import time
7
+ import threading
8
+ import re
9
+
10
+ # Configuration
11
+ REPO_DIR = "levanter"
12
+ DEFAULT_REPO = "https://github.com/priscy82/levanter.git"
13
+ PM2_APP_NAME = "whatsapp-bot"
14
+
15
+ def run_command(command, cwd=None, timeout=300):
16
+ """Execute a command and capture output with enhanced environment"""
17
+ try:
18
+ env = os.environ.copy()
19
+ # Set critical environment variables
20
+ env['PATH'] = '/usr/local/bin:/usr/bin:/bin'
21
+ env['PM2_HOME'] = '/tmp/.pm2'
22
+ env['NPM_CONFIG_CACHE'] = '/tmp/npm_cache'
23
+ env['YARN_CACHE_FOLDER'] = '/tmp/yarn_cache'
24
+ env['NODE_OPTIONS'] = '--max-old-space-size=512'
25
+
26
+ # Use shlex to properly handle command parsing
27
+ process = subprocess.run(
28
+ shlex.split(command),
29
+ stdout=subprocess.PIPE,
30
+ stderr=subprocess.PIPE,
31
+ text=True,
32
+ cwd=cwd,
33
+ timeout=timeout,
34
+ env=env
35
+ )
36
+ output = process.stdout
37
+ if process.stderr:
38
+ output += "\nERROR: " + process.stderr
39
+ return output
40
+ except subprocess.TimeoutExpired:
41
+ return "Command timed out after {} seconds".format(timeout)
42
+ except Exception as e:
43
+ return f"Command failed: {str(e)}"
44
+
45
+ def clone_and_install(repo_url):
46
+ """Clone repository and install dependencies with robust error handling"""
47
+ # Remove existing repo
48
+ if os.path.exists(REPO_DIR):
49
+ try:
50
+ shutil.rmtree(REPO_DIR)
51
+ except Exception as e:
52
+ return f"❌ Failed to remove existing directory: {str(e)}"
53
+
54
+ # Clone repo with retries
55
+ clone_attempts = 0
56
+ clone_output = ""
57
+ while clone_attempts < 3:
58
+ clone_cmd = f"git clone {repo_url} {REPO_DIR}"
59
+ clone_output = run_command(clone_cmd)
60
+ if os.path.exists(REPO_DIR):
61
+ break
62
+ clone_attempts += 1
63
+ time.sleep(2)
64
+
65
+ if not os.path.exists(REPO_DIR):
66
+ return f"❌ Clone failed after 3 attempts!\n{clone_output}"
67
+
68
+ # Navigate to repo and install dependencies
69
+ os.chdir(REPO_DIR)
70
+
71
+ # Check for lock files to determine package manager
72
+ package_manager = "yarn" if os.path.exists("yarn.lock") else "npm"
73
+
74
+ # Install dependencies
75
+ install_output = ""
76
+ if package_manager == "yarn":
77
+ install_output += run_command("yarn install --frozen-lockfile --network-timeout 300000")
78
+ else:
79
+ install_output += run_command("npm install --legacy-peer-deps --no-audit")
80
+
81
+ # Handle common errors automatically
82
+ if "ERR_PNPM" in install_output or "ERR!" in install_output:
83
+ install_output += "\n⚠️ Detected installation issues. Trying fallback..."
84
+ install_output += run_command("rm -rf node_modules")
85
+ install_output += run_command("rm -f package-lock.json yarn.lock")
86
+ install_output += run_command("npm cache clean --force")
87
+ install_output += run_command("npm install --legacy-peer-deps --force")
88
+
89
+ os.chdir("..")
90
+
91
+ return f"βœ… Repository cloned!\n{clone_output}\n\nπŸ’Ώ Dependencies installed ({package_manager})!\n{install_output}"
92
+
93
+ def start_bot():
94
+ """Start the bot using the appropriate package manager with PM2"""
95
+ if not os.path.exists(REPO_DIR):
96
+ return "❌ Repository not found! Clone first."
97
+
98
+ os.chdir(REPO_DIR)
99
+
100
+ # Determine package manager
101
+ package_manager = "yarn" if os.path.exists("yarn.lock") else "npm"
102
+
103
+ # Start the bot with PM2
104
+ if package_manager == "yarn":
105
+ start_cmd = f"pm2 start yarn --name {PM2_APP_NAME} -- start --watch --restart-delay=5000 --max-memory-restart 300M"
106
+ else:
107
+ start_cmd = f"pm2 start npm --name {PM2_APP_NAME} -- start --watch --restart-delay=5000 --max-memory-restart 300M"
108
+
109
+ start_output = run_command(start_cmd, timeout=60)
110
+
111
+ # Save PM2 process list
112
+ run_command("pm2 save")
113
+
114
+ os.chdir("..")
115
+
116
+ return f"πŸ€– Bot started with PM2!\n{start_output}"
117
+
118
+ def stop_bot():
119
+ """Stop the bot"""
120
+ if not os.path.exists(REPO_DIR):
121
+ return "❌ No active bot process"
122
+
123
+ os.chdir(REPO_DIR)
124
+ stop_output = run_command(f"pm2 stop {PM2_APP_NAME}")
125
+ os.chdir("..")
126
+ return f"πŸ›‘ Bot stopped!\n{stop_output}"
127
+
128
+ def get_logs():
129
+ """Get bot logs"""
130
+ if not os.path.exists(REPO_DIR):
131
+ return "No logs available"
132
+
133
+ os.chdir(REPO_DIR)
134
+ logs = run_command(f"pm2 logs {PM2_APP_NAME} --nostream --lines 100")
135
+ os.chdir("..")
136
+ return logs or "No logs available"
137
+
138
+ def get_bot_status():
139
+ """Get bot status with detailed information"""
140
+ if not os.path.exists(REPO_DIR):
141
+ return "Not deployed"
142
+
143
+ os.chdir(REPO_DIR)
144
+ status = run_command(f"pm2 status {PM2_APP_NAME}")
145
+ os.chdir("..")
146
+
147
+ # Enhance status output
148
+ if status:
149
+ if "online" in status:
150
+ return f"🟒 Bot is running!\n{status}"
151
+ elif "stopped" in status or "errored" in status:
152
+ return f"πŸ”΄ Bot is stopped!\n{status}"
153
+ elif "restarting" in status:
154
+ return f"🟑 Bot is restarting!\n{status}"
155
+ return "Status unknown"
156
+
157
+ def run_ssh_command(command):
158
+ """Execute custom command in repo directory"""
159
+ if not os.path.exists(REPO_DIR):
160
+ return "❌ Repository not found! Clone first."
161
+
162
+ os.chdir(REPO_DIR)
163
+ output = run_command(command)
164
+ os.chdir("..")
165
+ return output
166
+
167
+ def deploy_sequence(repo_url):
168
+ """Full deployment sequence with error recovery"""
169
+ output = clone_and_install(repo_url)
170
+ if "❌" in output:
171
+ return output
172
+
173
+ output += "\n\n" + start_bot()
174
+ return output
175
+
176
+ def monitor_bot():
177
+ """Background thread to monitor and restart bot"""
178
+ while True:
179
+ try:
180
+ if os.path.exists(REPO_DIR):
181
+ status = get_bot_status()
182
+ if "πŸ”΄" in status or "errored" in status:
183
+ print("Bot crashed! Restarting...")
184
+ start_bot()
185
+ except Exception as e:
186
+ print(f"Monitor error: {str(e)}")
187
+ time.sleep(30)
188
+
189
+ # Start monitoring thread
190
+ monitor_thread = threading.Thread(target=monitor_bot, daemon=True)
191
+ monitor_thread.start()
192
+
193
+ # Gradio Interface
194
+ with gr.Blocks(title="WhatsApp Bot Deployer", theme="soft") as demo:
195
+ gr.Markdown("""
196
+ # πŸ€– WhatsApp Bot Deployment Panel
197
+ **Repository:** https://github.com/priscy82/levanter.git
198
+ **Node Version:** 23 | **PM2:** Installed | **Auto-Restart:** Enabled
199
+ """)
200
+
201
+ with gr.Tab("πŸš€ Deploy Bot"):
202
+ repo_input = gr.Textbox(
203
+ label="Git Repository URL",
204
+ value=DEFAULT_REPO,
205
+ interactive=False
206
+ )
207
+ deploy_btn = gr.Button("πŸš€ Clone & Deploy", variant="primary")
208
+ deploy_output = gr.Textbox(label="Deployment Logs", interactive=False, lines=12)
209
+
210
+ with gr.Tab("βš™οΈ Bot Controls"):
211
+ with gr.Row():
212
+ status_btn = gr.Button("πŸ”„ Refresh Status", variant="secondary")
213
+ start_btn = gr.Button("▢️ Start Bot", variant="primary")
214
+ stop_btn = gr.Button("⏹️ Stop Bot", variant="stop")
215
+ restart_btn = gr.Button("πŸ” Restart Bot", variant="primary")
216
+
217
+ status_output = gr.Textbox(label="Current Status", interactive=False, lines=4)
218
+ controls_output = gr.Textbox(label="Action Output", interactive=False, lines=4)
219
+
220
+ gr.Markdown("### πŸ“‹ Log Viewer")
221
+ with gr.Row():
222
+ log_lines = gr.Slider(50, 500, value=100, label="Log Lines")
223
+ log_btn = gr.Button("πŸ“‹ Get Latest Logs")
224
+ log_output = gr.Textbox(label="Bot Logs", interactive=False, lines=18)
225
+
226
+ with gr.Tab("πŸ’» Terminal"):
227
+ gr.Markdown("### ⚑ Run Custom Commands")
228
+ cmd_input = gr.Textbox(
229
+ label="Enter command",
230
+ placeholder="npm install package-name",
231
+ lines=1
232
+ )
233
+ run_btn = gr.Button("▢️ Run", variant="primary")
234
+ terminal_output = gr.Textbox(label="Output", interactive=False, lines=12)
235
+
236
+ gr.Markdown("### πŸš€ Common Commands")
237
+ examples = gr.Examples(
238
+ examples=[
239
+ ["npm install --legacy-peer-deps"],
240
+ ["yarn install --frozen-lockfile"],
241
+ ["git pull"],
242
+ ["node --version"],
243
+ ["npm run build"],
244
+ ["pm2 status"],
245
+ ["pm2 logs"]
246
+ ],
247
+ inputs=cmd_input,
248
+ label="Click to insert command"
249
+ )
250
+
251
+ # Deployment tab actions
252
+ deploy_btn.click(
253
+ deploy_sequence,
254
+ inputs=repo_input,
255
+ outputs=deploy_output
256
+ )
257
+
258
+ # Status and control actions
259
+ status_btn.click(
260
+ get_bot_status,
261
+ outputs=status_output
262
+ )
263
+ start_btn.click(
264
+ start_bot,
265
+ outputs=controls_output
266
+ ).then(
267
+ get_bot_status,
268
+ outputs=status_output
269
+ )
270
+ stop_btn.click(
271
+ stop_bot,
272
+ outputs=controls_output
273
+ ).then(
274
+ get_bot_status,
275
+ outputs=status_output
276
+ )
277
+ restart_btn.click(
278
+ lambda: run_ssh_command(f"pm2 restart {PM2_APP_NAME}"),
279
+ outputs=controls_output
280
+ ).then(
281
+ get_bot_status,
282
+ outputs=status_output
283
+ )
284
+
285
+ # Log actions
286
+ log_btn.click(
287
+ get_logs,
288
+ outputs=log_output
289
+ )
290
+
291
+ # Terminal actions
292
+ run_btn.click(
293
+ run_ssh_command,
294
+ inputs=cmd_input,
295
+ outputs=terminal_output
296
+ )
297
+
298
+ if __name__ == "__main__":
299
+ demo.launch(server_port=7860, server_name="0.0.0.0")