File size: 12,958 Bytes
fbe70cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
import os
import subprocess
import time
import socket
import signal
import sys
from pyngrok import ngrok
import gradio as gr

# Get ngrok authtoken from environment
NGROK_AUTHTOKEN = os.getenv("NGROK_AUTHTOKEN")

# Global process references for cleanup
processes = {}

def is_port_open(port, host='localhost', timeout=2):
    """Check if a port is open and listening"""
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.settimeout(timeout)
            result = sock.connect_ex((host, port))
            return result == 0
    except:
        return False

def cleanup_processes():
    """Cleanup all processes on exit"""
    print("\nCleaning up processes...")
    for name, proc in processes.items():
        try:
            if proc and proc.poll() is None:
                print(f"Terminating {name}...")
                proc.terminate()
                proc.wait(timeout=5)
        except:
            try:
                proc.kill()
            except:
                pass

def signal_handler(sig, frame):
    """Handle interrupt signals"""
    cleanup_processes()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

def cleanup_previous_sessions():
    """Clean up any previous sessions"""
    print("Cleaning up previous sessions...")
    
    commands = [
        ["pkill", "-f", "x11vnc"],
        ["pkill", "-f", "websockify"],
        ["pkill", "-f", "Xvfb"],
        ["pkill", "-f", "fluxbox"],
        ["pkill", "-f", "organichits-exchanger"],
        ["fuser", "-k", "5901/tcp"],
        ["fuser", "-k", "8081/tcp"]
    ]
    
    for cmd in commands:
        try:
            subprocess.run(cmd, capture_output=True, timeout=5)
        except:
            pass
    
    time.sleep(2)

def setup_display():
    """Setup virtual display with proper settings for Electron/Chromium"""
    print("Setting up virtual display for Electron app...")
    
    # Kill any existing Xvfb on display 99
    subprocess.run(["pkill", "-f", "Xvfb :99"], capture_output=True)
    time.sleep(1)
    
    # Start Xvfb with settings optimized for Chromium
    xvfb_process = subprocess.Popen([
        "Xvfb", ":99",
        "-screen", "0", "1280x720x24",
        "-ac",
        "-nolisten", "tcp",
        "-dpi", "96",
        "+extension", "RANDR"
    ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    processes['xvfb'] = xvfb_process
    
    # Wait for X server to be ready
    time.sleep(3)
    
    # Set display environment globally
    os.environ['DISPLAY'] = ':99'
    
    # Verify display is working
    try:
        result = subprocess.run(
            ["xdpyinfo", "-display", ":99"],
            capture_output=True,
            timeout=5
        )
        if result.returncode == 0:
            print("βœ“ Virtual display :99 is ready")
        else:
            print("⚠ Display may not be fully ready")
    except:
        print("⚠ Could not verify display")
    
    # Start fluxbox window manager
    print("Starting window manager...")
    fluxbox_process = subprocess.Popen(
        ["fluxbox"],
        env={**os.environ, 'DISPLAY': ':99'},
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    
    processes['fluxbox'] = fluxbox_process
    time.sleep(2)
    
    return True

def start_vnc_server():
    """Start VNC server connected to virtual display"""
    print("Starting VNC server...")
    
    # Start x11vnc
    vnc_process = subprocess.Popen([
        "x11vnc",
        "-display", ":99",
        "-forever",
        "-shared",
        "-nopw",
        "-listen", "0.0.0.0",
        "-rfbport", "5901",
        "-xkb",
        "-noxrecord",
        "-noxfixes",
        "-noxdamage",
        "-wait", "10",
        "-defer", "10"
    ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    processes['vnc'] = vnc_process
    
    # Wait for VNC to start
    for i in range(15):
        if is_port_open(5901):
            print("βœ“ VNC server started on port 5901")
            return True
        time.sleep(1)
    
    print("βœ— VNC server failed to start")
    return False

def start_organichits_app():
    """Start OrganicHits Exchanger with proper Electron/Chromium flags"""
    print("Starting OrganicHits Exchanger...")
    
    # Ensure display is set
    os.environ['DISPLAY'] = ':99'
    
    # Path to the binary and its directory
    app_dir = "./OrganicHits"
    app_path = os.path.join(app_dir, "organichits-exchanger")
    
    # Make sure it's executable
    os.chmod(app_path, 0o755)
    
    # Verify critical files exist
    critical_files = ['icudtl.dat', 'resources.pak', 'v8_context_snapshot.bin']
    for file in critical_files:
        file_path = os.path.join(app_dir, file)
        if not os.path.exists(file_path):
            print(f"⚠ Warning: Missing critical file: {file}")
    
    # Comprehensive Chromium flags for containerized Electron apps
    app_process = subprocess.Popen([
        app_path,
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-dev-shm-usage",
        "--disable-gpu",
        "--disable-software-rasterizer",
        "--disable-gpu-compositing",
        "--enable-features=UseOzonePlatform",
        "--ozone-platform=x11",
        "--disable-features=VizDisplayCompositor",
        "--in-process-gpu",
        "--disable-accelerated-2d-canvas",
        "--disable-accelerated-video-decode",
        "--disable-accelerated-video-encode",
        "--no-first-run",
        "--no-default-browser-check",
        "--disable-background-timer-throttling",
        "--disable-backgrounding-occluded-windows",
        "--disable-renderer-backgrounding",
        "--disable-breakpad"
    ],
    env={
        **os.environ,
        'DISPLAY': ':99',
        'ELECTRON_DISABLE_SANDBOX': '1',
        'ELECTRON_ENABLE_LOGGING': '1',
        'LD_LIBRARY_PATH': app_dir
    },
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    cwd=app_dir
    )
    
    processes['app'] = app_process
    
    # Give app time to start
    time.sleep(5)
    
    # Check if app is still running
    if app_process.poll() is None:
        print("βœ“ OrganicHits application started")
        return True
    else:
        stdout, stderr = app_process.communicate(timeout=1)
        print(f"βœ— Application failed to start")
        print(f"stdout: {stdout.decode()[:500]}")
        print(f"stderr: {stderr.decode()[:500]}")
        return False

def start_websockify():
    """Start websockify proxy for web access"""
    print("Starting websockify on port 8081...")
    
    websockify_process = subprocess.Popen([
        "websockify",
        "8081",
        "localhost:5901",
        "--web=/usr/share/novnc/",
        "--verbose"
    ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    processes['websockify'] = websockify_process
    
    # Wait for websockify to start
    for i in range(10):
        if is_port_open(8081):
            print("βœ“ Websockify started on port 8081")
            return True
        time.sleep(1)
    
    print("βœ— Websockify failed to start")
    return False

def start_vnc_and_ngrok():
    """Main function to start everything"""
    if not NGROK_AUTHTOKEN:
        return "❌ Error: NGROK_AUTHTOKEN not found in environment secrets!"

    try:
        # Step 1: Cleanup
        cleanup_previous_sessions()
        
        # Step 2: Setup display
        if not setup_display():
            return "❌ Failed to setup virtual display"
        
        # Step 3: Start VNC
        if not start_vnc_server():
            return "❌ Failed to start VNC server"
        
        # Step 4: Start websockify
        if not start_websockify():
            return "❌ Failed to start websockify"
        
        # Step 5: Start the application
        app_started = start_organichits_app()
        app_status = "βœ“ Running" if app_started else "⚠ May have failed (check logs)"
        
        # Step 6: Setup ngrok
        print("Creating ngrok tunnel...")
        try:
            ngrok.set_auth_token(NGROK_AUTHTOKEN)
            public_url = ngrok.connect(8081, "http")
            novnc_url = f"{public_url}/vnc.html?autoconnect=true"
            
            return f"""βœ… VNC Session Started Successfully!

🌐 Access Your Application:
{novnc_url}

πŸ“Š Status:
β€’ Virtual Display: βœ“ Running on :99
β€’ VNC Server: βœ“ Running on port 5901
β€’ Websockify: βœ“ Running on port 8081
β€’ OrganicHits App: {app_status}

πŸ“± Click the URL above to access your OrganicHits Exchanger!

⚠️ Notes:
- First load may take 10-20 seconds
- Click "Connect" if not auto-connected
- No password required
- Keep this tab open to maintain the session

πŸ”§ Troubleshooting:
- If black screen persists, wait 30 seconds and refresh
- Check "Service Status" below
- Application logs are being captured
"""
            
        except Exception as e:
            return f"❌ Failed to create ngrok tunnel: {e}"

    except Exception as e:
        return f"❌ Unexpected error: {str(e)}"

def check_services():
    """Check status of all services"""
    services = {
        'Virtual Display (Xvfb)': is_port_open(6099) or ('xvfb' in processes and processes['xvfb'].poll() is None),
        'VNC Server': is_port_open(5901),
        'Websockify': is_port_open(8081),
        'OrganicHits App': 'app' in processes and processes['app'].poll() is None
    }
    
    status_text = "πŸ” Service Status:\n\n"
    for service, running in services.items():
        emoji = "βœ…" if running else "❌"
        status_text += f"{emoji} {service}\n"
    
    return status_text

def get_app_logs():
    """Get application logs"""
    if 'app' not in processes or processes['app'].poll() is not None:
        return "Application not running or has stopped"
    
    try:
        # Try to get recent output
        return "Application is running. Check console for detailed logs."
    except:
        return "Could not retrieve logs"

def main():
    with gr.Blocks(title="OrganicHits VNC Remote Desktop", theme=gr.themes.Soft()) as demo:
        gr.Markdown("""
        # πŸ–₯️ OrganicHits Exchanger - Remote Desktop
        
        Run OrganicHits Exchanger in your browser via VNC
        """)
        
        with gr.Row():
            with gr.Column(scale=2):
                output = gr.Textbox(
                    label="🌐 Access Information",
                    lines=15,
                    placeholder="Click 'Start VNC Session' to begin...",
                    show_copy_button=True
                )
                
                with gr.Row():
                    start_button = gr.Button(
                        "πŸš€ Start VNC Session",
                        variant="primary",
                        size="lg",
                        scale=2
                    )
                    check_btn = gr.Button(
                        "πŸ” Check Status",
                        variant="secondary",
                        scale=1
                    )
            
            with gr.Column(scale=1):
                status = gr.Textbox(
                    label="πŸ“Š Service Status",
                    lines=8,
                    value="Services not started yet..."
                )
                
                gr.Markdown("""
                ### ⏱️ Expected Timeline:
                - Display setup: 3-5s
                - VNC start: 2-3s
                - App launch: 5-10s
                - **Total: ~20-30 seconds**
                """)
        
        gr.Markdown("""
        ---
        ### πŸ“‹ How to Use:
        
        1. **Click "Start VNC Session"** and wait ~30 seconds
        2. **Click the URL** that appears above
        3. **Wait for connection** - you'll see the desktop
        4. **OrganicHits will auto-launch** in the window
        
        ### 🎯 What You'll See:
        - A desktop environment (Fluxbox)
        - OrganicHits Exchanger window
        - You can interact with it normally!
        
        ### ⚠️ Important Notes:
        - Keep this browser tab open
        - No authentication (temporary session)
        - Session ends if you close this page
        - First connection may show black screen for 10-20 seconds (normal!)
        
        ### πŸ”§ Troubleshooting:
        - **Black screen?** Wait 30 seconds and refresh the VNC page
        - **Can't connect?** Check if NGROK_AUTHTOKEN is set in Secrets
        - **App not showing?** Click "Check Status" to diagnose
        """)
        
        start_button.click(
            fn=start_vnc_and_ngrok,
            outputs=output
        )
        
        check_btn.click(
            fn=check_services,
            outputs=status
        )

    # Launch Gradio
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False,
        show_error=True
    )

if __name__ == "__main__":
    main()