Oct / app.py
huijio's picture
Create app.py
fbe70cb verified
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()