bbefb8807b
WhatsApp webhook, admin dashboard, public frontend, media handling, LLM auto-summarize, hall/stand detection, iOS-style backend
125 lines
3.4 KiB
Python
125 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
BSN Intake Supervisor — starts Flask app + Cloudflare tunnel.
|
|
Keeps both alive, restarts on crash, prints the public URL.
|
|
"""
|
|
|
|
import subprocess
|
|
import time
|
|
import os
|
|
import sys
|
|
import re
|
|
|
|
BASE_DIR = "/home/hermes/workspace/bsn-chatbot"
|
|
VENV_PYTHON = "/home/hermes/venv/bin/python3"
|
|
CLOUDFLARED = "/home/hermes/.local/bin/cloudflared"
|
|
|
|
# Load .env
|
|
env = os.environ.copy()
|
|
env_file = os.path.join(BASE_DIR, ".env")
|
|
if os.path.exists(env_file):
|
|
with open(env_file) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith("#") and "=" in line:
|
|
key, _, val = line.partition("=")
|
|
key = key.strip()
|
|
val = val.strip().strip('"').strip("'")
|
|
env[key] = val
|
|
|
|
def start_flask():
|
|
"""Start the Flask app as a subprocess."""
|
|
proc = subprocess.Popen(
|
|
[VENV_PYTHON, os.path.join(BASE_DIR, "app.py")],
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
)
|
|
return proc
|
|
|
|
def start_cloudflared():
|
|
"""Start the Cloudflare tunnel."""
|
|
proc = subprocess.Popen(
|
|
[CLOUDFLARED, "tunnel", "--url", "http://127.0.0.1:5002"],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
)
|
|
return proc
|
|
|
|
|
|
def main():
|
|
LOG = open("/tmp/bsn_supervisor.log", "w")
|
|
def log(msg):
|
|
LOG.write(msg + "\n")
|
|
LOG.flush()
|
|
|
|
log("🚀 BSN Intake Supervisor starting...")
|
|
|
|
# Start Flask
|
|
flask = start_flask()
|
|
log("[flask] started")
|
|
time.sleep(2)
|
|
|
|
# Verify Flask is up
|
|
import urllib.request
|
|
try:
|
|
urllib.request.urlopen("http://127.0.0.1:5002/health", timeout=5)
|
|
log("[flask] health check: OK")
|
|
except Exception as e:
|
|
log(f"[flask] health check FAILED: {e}")
|
|
|
|
# Start Cloudflared
|
|
cf = start_cloudflared()
|
|
log("[cloudflared] started")
|
|
|
|
# Wait for tunnel URL to appear in output
|
|
tunnel_url = None
|
|
deadline = time.time() + 20
|
|
buffer = ""
|
|
while time.time() < deadline:
|
|
line = cf.stdout.readline() if cf.stdout else ""
|
|
if line:
|
|
buffer += line
|
|
m = re.search(r"(https://[a-z-]+\.trycloudflare\.com)", buffer)
|
|
if m:
|
|
tunnel_url = m.group(1)
|
|
break
|
|
if flask.poll() is not None:
|
|
log(f"[flask] CRASHED! exit={flask.returncode}")
|
|
break
|
|
if cf.poll() is not None:
|
|
log(f"[cloudflared] CRASHED! exit={cf.returncode}")
|
|
break
|
|
|
|
if tunnel_url:
|
|
log(f"\n✅ TUNNEL: {tunnel_url}")
|
|
log(f" Webhook: {tunnel_url}/whatsapp/webhook")
|
|
log(f" Admin: {tunnel_url}/admin")
|
|
else:
|
|
log("❌ Failed to get tunnel URL")
|
|
|
|
log("\n[supervisor] Running. Press Ctrl+C to stop.\n")
|
|
|
|
# Keep running, restart children if they die
|
|
try:
|
|
while True:
|
|
if flask.poll() is not None:
|
|
log("[flask] died, restarting...")
|
|
flask = start_flask()
|
|
if cf.poll() is not None:
|
|
log("[cloudflared] died, restarting...")
|
|
cf = start_cloudflared()
|
|
time.sleep(5)
|
|
except KeyboardInterrupt:
|
|
log("\n[supervisor] Shutting down...")
|
|
flask.terminate()
|
|
cf.terminate()
|
|
flask.wait()
|
|
cf.wait()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|