#!/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()