huijio commited on
Commit
fbe70cb
Β·
verified Β·
1 Parent(s): 8d02f40

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +434 -0
app.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import time
4
+ import socket
5
+ import signal
6
+ import sys
7
+ from pyngrok import ngrok
8
+ import gradio as gr
9
+
10
+ # Get ngrok authtoken from environment
11
+ NGROK_AUTHTOKEN = os.getenv("NGROK_AUTHTOKEN")
12
+
13
+ # Global process references for cleanup
14
+ processes = {}
15
+
16
+ def is_port_open(port, host='localhost', timeout=2):
17
+ """Check if a port is open and listening"""
18
+ try:
19
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
20
+ sock.settimeout(timeout)
21
+ result = sock.connect_ex((host, port))
22
+ return result == 0
23
+ except:
24
+ return False
25
+
26
+ def cleanup_processes():
27
+ """Cleanup all processes on exit"""
28
+ print("\nCleaning up processes...")
29
+ for name, proc in processes.items():
30
+ try:
31
+ if proc and proc.poll() is None:
32
+ print(f"Terminating {name}...")
33
+ proc.terminate()
34
+ proc.wait(timeout=5)
35
+ except:
36
+ try:
37
+ proc.kill()
38
+ except:
39
+ pass
40
+
41
+ def signal_handler(sig, frame):
42
+ """Handle interrupt signals"""
43
+ cleanup_processes()
44
+ sys.exit(0)
45
+
46
+ signal.signal(signal.SIGINT, signal_handler)
47
+ signal.signal(signal.SIGTERM, signal_handler)
48
+
49
+ def cleanup_previous_sessions():
50
+ """Clean up any previous sessions"""
51
+ print("Cleaning up previous sessions...")
52
+
53
+ commands = [
54
+ ["pkill", "-f", "x11vnc"],
55
+ ["pkill", "-f", "websockify"],
56
+ ["pkill", "-f", "Xvfb"],
57
+ ["pkill", "-f", "fluxbox"],
58
+ ["pkill", "-f", "organichits-exchanger"],
59
+ ["fuser", "-k", "5901/tcp"],
60
+ ["fuser", "-k", "8081/tcp"]
61
+ ]
62
+
63
+ for cmd in commands:
64
+ try:
65
+ subprocess.run(cmd, capture_output=True, timeout=5)
66
+ except:
67
+ pass
68
+
69
+ time.sleep(2)
70
+
71
+ def setup_display():
72
+ """Setup virtual display with proper settings for Electron/Chromium"""
73
+ print("Setting up virtual display for Electron app...")
74
+
75
+ # Kill any existing Xvfb on display 99
76
+ subprocess.run(["pkill", "-f", "Xvfb :99"], capture_output=True)
77
+ time.sleep(1)
78
+
79
+ # Start Xvfb with settings optimized for Chromium
80
+ xvfb_process = subprocess.Popen([
81
+ "Xvfb", ":99",
82
+ "-screen", "0", "1280x720x24",
83
+ "-ac",
84
+ "-nolisten", "tcp",
85
+ "-dpi", "96",
86
+ "+extension", "RANDR"
87
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
88
+
89
+ processes['xvfb'] = xvfb_process
90
+
91
+ # Wait for X server to be ready
92
+ time.sleep(3)
93
+
94
+ # Set display environment globally
95
+ os.environ['DISPLAY'] = ':99'
96
+
97
+ # Verify display is working
98
+ try:
99
+ result = subprocess.run(
100
+ ["xdpyinfo", "-display", ":99"],
101
+ capture_output=True,
102
+ timeout=5
103
+ )
104
+ if result.returncode == 0:
105
+ print("βœ“ Virtual display :99 is ready")
106
+ else:
107
+ print("⚠ Display may not be fully ready")
108
+ except:
109
+ print("⚠ Could not verify display")
110
+
111
+ # Start fluxbox window manager
112
+ print("Starting window manager...")
113
+ fluxbox_process = subprocess.Popen(
114
+ ["fluxbox"],
115
+ env={**os.environ, 'DISPLAY': ':99'},
116
+ stdout=subprocess.PIPE,
117
+ stderr=subprocess.PIPE
118
+ )
119
+
120
+ processes['fluxbox'] = fluxbox_process
121
+ time.sleep(2)
122
+
123
+ return True
124
+
125
+ def start_vnc_server():
126
+ """Start VNC server connected to virtual display"""
127
+ print("Starting VNC server...")
128
+
129
+ # Start x11vnc
130
+ vnc_process = subprocess.Popen([
131
+ "x11vnc",
132
+ "-display", ":99",
133
+ "-forever",
134
+ "-shared",
135
+ "-nopw",
136
+ "-listen", "0.0.0.0",
137
+ "-rfbport", "5901",
138
+ "-xkb",
139
+ "-noxrecord",
140
+ "-noxfixes",
141
+ "-noxdamage",
142
+ "-wait", "10",
143
+ "-defer", "10"
144
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
145
+
146
+ processes['vnc'] = vnc_process
147
+
148
+ # Wait for VNC to start
149
+ for i in range(15):
150
+ if is_port_open(5901):
151
+ print("βœ“ VNC server started on port 5901")
152
+ return True
153
+ time.sleep(1)
154
+
155
+ print("βœ— VNC server failed to start")
156
+ return False
157
+
158
+ def start_organichits_app():
159
+ """Start OrganicHits Exchanger with proper Electron/Chromium flags"""
160
+ print("Starting OrganicHits Exchanger...")
161
+
162
+ # Ensure display is set
163
+ os.environ['DISPLAY'] = ':99'
164
+
165
+ # Path to the binary and its directory
166
+ app_dir = "./OrganicHits"
167
+ app_path = os.path.join(app_dir, "organichits-exchanger")
168
+
169
+ # Make sure it's executable
170
+ os.chmod(app_path, 0o755)
171
+
172
+ # Verify critical files exist
173
+ critical_files = ['icudtl.dat', 'resources.pak', 'v8_context_snapshot.bin']
174
+ for file in critical_files:
175
+ file_path = os.path.join(app_dir, file)
176
+ if not os.path.exists(file_path):
177
+ print(f"⚠ Warning: Missing critical file: {file}")
178
+
179
+ # Comprehensive Chromium flags for containerized Electron apps
180
+ app_process = subprocess.Popen([
181
+ app_path,
182
+ "--no-sandbox",
183
+ "--disable-setuid-sandbox",
184
+ "--disable-dev-shm-usage",
185
+ "--disable-gpu",
186
+ "--disable-software-rasterizer",
187
+ "--disable-gpu-compositing",
188
+ "--enable-features=UseOzonePlatform",
189
+ "--ozone-platform=x11",
190
+ "--disable-features=VizDisplayCompositor",
191
+ "--in-process-gpu",
192
+ "--disable-accelerated-2d-canvas",
193
+ "--disable-accelerated-video-decode",
194
+ "--disable-accelerated-video-encode",
195
+ "--no-first-run",
196
+ "--no-default-browser-check",
197
+ "--disable-background-timer-throttling",
198
+ "--disable-backgrounding-occluded-windows",
199
+ "--disable-renderer-backgrounding",
200
+ "--disable-breakpad"
201
+ ],
202
+ env={
203
+ **os.environ,
204
+ 'DISPLAY': ':99',
205
+ 'ELECTRON_DISABLE_SANDBOX': '1',
206
+ 'ELECTRON_ENABLE_LOGGING': '1',
207
+ 'LD_LIBRARY_PATH': app_dir
208
+ },
209
+ stdout=subprocess.PIPE,
210
+ stderr=subprocess.PIPE,
211
+ cwd=app_dir
212
+ )
213
+
214
+ processes['app'] = app_process
215
+
216
+ # Give app time to start
217
+ time.sleep(5)
218
+
219
+ # Check if app is still running
220
+ if app_process.poll() is None:
221
+ print("βœ“ OrganicHits application started")
222
+ return True
223
+ else:
224
+ stdout, stderr = app_process.communicate(timeout=1)
225
+ print(f"βœ— Application failed to start")
226
+ print(f"stdout: {stdout.decode()[:500]}")
227
+ print(f"stderr: {stderr.decode()[:500]}")
228
+ return False
229
+
230
+ def start_websockify():
231
+ """Start websockify proxy for web access"""
232
+ print("Starting websockify on port 8081...")
233
+
234
+ websockify_process = subprocess.Popen([
235
+ "websockify",
236
+ "8081",
237
+ "localhost:5901",
238
+ "--web=/usr/share/novnc/",
239
+ "--verbose"
240
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
241
+
242
+ processes['websockify'] = websockify_process
243
+
244
+ # Wait for websockify to start
245
+ for i in range(10):
246
+ if is_port_open(8081):
247
+ print("βœ“ Websockify started on port 8081")
248
+ return True
249
+ time.sleep(1)
250
+
251
+ print("βœ— Websockify failed to start")
252
+ return False
253
+
254
+ def start_vnc_and_ngrok():
255
+ """Main function to start everything"""
256
+ if not NGROK_AUTHTOKEN:
257
+ return "❌ Error: NGROK_AUTHTOKEN not found in environment secrets!"
258
+
259
+ try:
260
+ # Step 1: Cleanup
261
+ cleanup_previous_sessions()
262
+
263
+ # Step 2: Setup display
264
+ if not setup_display():
265
+ return "❌ Failed to setup virtual display"
266
+
267
+ # Step 3: Start VNC
268
+ if not start_vnc_server():
269
+ return "❌ Failed to start VNC server"
270
+
271
+ # Step 4: Start websockify
272
+ if not start_websockify():
273
+ return "❌ Failed to start websockify"
274
+
275
+ # Step 5: Start the application
276
+ app_started = start_organichits_app()
277
+ app_status = "βœ“ Running" if app_started else "⚠ May have failed (check logs)"
278
+
279
+ # Step 6: Setup ngrok
280
+ print("Creating ngrok tunnel...")
281
+ try:
282
+ ngrok.set_auth_token(NGROK_AUTHTOKEN)
283
+ public_url = ngrok.connect(8081, "http")
284
+ novnc_url = f"{public_url}/vnc.html?autoconnect=true"
285
+
286
+ return f"""βœ… VNC Session Started Successfully!
287
+
288
+ 🌐 Access Your Application:
289
+ {novnc_url}
290
+
291
+ πŸ“Š Status:
292
+ β€’ Virtual Display: βœ“ Running on :99
293
+ β€’ VNC Server: βœ“ Running on port 5901
294
+ β€’ Websockify: βœ“ Running on port 8081
295
+ β€’ OrganicHits App: {app_status}
296
+
297
+ πŸ“± Click the URL above to access your OrganicHits Exchanger!
298
+
299
+ ⚠️ Notes:
300
+ - First load may take 10-20 seconds
301
+ - Click "Connect" if not auto-connected
302
+ - No password required
303
+ - Keep this tab open to maintain the session
304
+
305
+ πŸ”§ Troubleshooting:
306
+ - If black screen persists, wait 30 seconds and refresh
307
+ - Check "Service Status" below
308
+ - Application logs are being captured
309
+ """
310
+
311
+ except Exception as e:
312
+ return f"❌ Failed to create ngrok tunnel: {e}"
313
+
314
+ except Exception as e:
315
+ return f"❌ Unexpected error: {str(e)}"
316
+
317
+ def check_services():
318
+ """Check status of all services"""
319
+ services = {
320
+ 'Virtual Display (Xvfb)': is_port_open(6099) or ('xvfb' in processes and processes['xvfb'].poll() is None),
321
+ 'VNC Server': is_port_open(5901),
322
+ 'Websockify': is_port_open(8081),
323
+ 'OrganicHits App': 'app' in processes and processes['app'].poll() is None
324
+ }
325
+
326
+ status_text = "πŸ” Service Status:\n\n"
327
+ for service, running in services.items():
328
+ emoji = "βœ…" if running else "❌"
329
+ status_text += f"{emoji} {service}\n"
330
+
331
+ return status_text
332
+
333
+ def get_app_logs():
334
+ """Get application logs"""
335
+ if 'app' not in processes or processes['app'].poll() is not None:
336
+ return "Application not running or has stopped"
337
+
338
+ try:
339
+ # Try to get recent output
340
+ return "Application is running. Check console for detailed logs."
341
+ except:
342
+ return "Could not retrieve logs"
343
+
344
+ def main():
345
+ with gr.Blocks(title="OrganicHits VNC Remote Desktop", theme=gr.themes.Soft()) as demo:
346
+ gr.Markdown("""
347
+ # πŸ–₯️ OrganicHits Exchanger - Remote Desktop
348
+
349
+ Run OrganicHits Exchanger in your browser via VNC
350
+ """)
351
+
352
+ with gr.Row():
353
+ with gr.Column(scale=2):
354
+ output = gr.Textbox(
355
+ label="🌐 Access Information",
356
+ lines=15,
357
+ placeholder="Click 'Start VNC Session' to begin...",
358
+ show_copy_button=True
359
+ )
360
+
361
+ with gr.Row():
362
+ start_button = gr.Button(
363
+ "πŸš€ Start VNC Session",
364
+ variant="primary",
365
+ size="lg",
366
+ scale=2
367
+ )
368
+ check_btn = gr.Button(
369
+ "πŸ” Check Status",
370
+ variant="secondary",
371
+ scale=1
372
+ )
373
+
374
+ with gr.Column(scale=1):
375
+ status = gr.Textbox(
376
+ label="πŸ“Š Service Status",
377
+ lines=8,
378
+ value="Services not started yet..."
379
+ )
380
+
381
+ gr.Markdown("""
382
+ ### ⏱️ Expected Timeline:
383
+ - Display setup: 3-5s
384
+ - VNC start: 2-3s
385
+ - App launch: 5-10s
386
+ - **Total: ~20-30 seconds**
387
+ """)
388
+
389
+ gr.Markdown("""
390
+ ---
391
+ ### πŸ“‹ How to Use:
392
+
393
+ 1. **Click "Start VNC Session"** and wait ~30 seconds
394
+ 2. **Click the URL** that appears above
395
+ 3. **Wait for connection** - you'll see the desktop
396
+ 4. **OrganicHits will auto-launch** in the window
397
+
398
+ ### 🎯 What You'll See:
399
+ - A desktop environment (Fluxbox)
400
+ - OrganicHits Exchanger window
401
+ - You can interact with it normally!
402
+
403
+ ### ⚠️ Important Notes:
404
+ - Keep this browser tab open
405
+ - No authentication (temporary session)
406
+ - Session ends if you close this page
407
+ - First connection may show black screen for 10-20 seconds (normal!)
408
+
409
+ ### πŸ”§ Troubleshooting:
410
+ - **Black screen?** Wait 30 seconds and refresh the VNC page
411
+ - **Can't connect?** Check if NGROK_AUTHTOKEN is set in Secrets
412
+ - **App not showing?** Click "Check Status" to diagnose
413
+ """)
414
+
415
+ start_button.click(
416
+ fn=start_vnc_and_ngrok,
417
+ outputs=output
418
+ )
419
+
420
+ check_btn.click(
421
+ fn=check_services,
422
+ outputs=status
423
+ )
424
+
425
+ # Launch Gradio
426
+ demo.launch(
427
+ server_name="0.0.0.0",
428
+ server_port=7860,
429
+ share=False,
430
+ show_error=True
431
+ )
432
+
433
+ if __name__ == "__main__":
434
+ main()