aboutsummaryrefslogtreecommitdiffstats
path: root/run.py
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-10-29 19:58:05 -0700
committerPinapelz <yukais@pinapelz.com>2026-10-29 19:58:05 -0700
commit96bfe2e1505b4ae250f2d5201f9541ceb01cec84 (patch)
treeb2164a76dc3aa5b69e160d1135d3385bcbe6108d /run.py
parent150858067059ba615c548f826bb343589993b4bb (diff)
ts web slopify code
Diffstat (limited to 'run.py')
-rw-r--r--run.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..0ba7220
--- /dev/null
+++ b/run.py
@@ -0,0 +1,121 @@
+"""Start the dashboard and the browser voice gateway with one command.
+
+ uv run run.py
+
+With web voice enabled (config.toml [voice] enabled = true) the Node gateway
+listens on the public port (web.port), handles /ws/voice and forwards every
+other request to Flask on 127.0.0.1:web.dashboard_port. With voice disabled
+(or Node missing) only the dashboard runs, directly on the public port.
+"""
+
+import os
+import shutil
+import signal
+import subprocess
+import sys
+import time
+
+from config import BASE_DIR, CONFIG
+
+GATEWAY_DIR = BASE_DIR / "gateway"
+MIN_NODE = (22, 12)
+
+
+def log(message):
+ print(f"[run] {message}", flush=True)
+
+
+def node_ok(node):
+ try:
+ out = subprocess.run([node, "--version"], capture_output=True, text=True, check=True).stdout
+ major, minor = (int(part) for part in out.strip().lstrip("v").split(".")[:2])
+ return (major, minor) >= MIN_NODE, out.strip()
+ except (OSError, ValueError, subprocess.CalledProcessError):
+ return False, "unknown"
+
+
+def ensure_gateway_deps():
+ if (GATEWAY_DIR / "node_modules" / "@honeybbq" / "teamspeak").is_dir():
+ return True
+ npm = shutil.which("npm")
+ if not npm:
+ log("npm not found; cannot install the voice gateway dependencies")
+ return False
+ lock = (GATEWAY_DIR / "package-lock.json").exists()
+ cmd = [npm, "ci" if lock else "install", "--omit=dev", "--no-audit", "--no-fund"]
+ log(f"installing voice gateway dependencies: {' '.join(cmd[1:])}")
+ return subprocess.run(cmd, cwd=GATEWAY_DIR).returncode == 0
+
+
+def gateway_command():
+ """The Node command for the gateway, or None when voice cannot run."""
+ if not CONFIG["voice"].get("enabled"):
+ log("web voice disabled in config.toml; starting the dashboard only")
+ return None
+ node = shutil.which("node")
+ if not node:
+ log("Node.js not found; starting the dashboard only (install Node >= 22.12 for web voice)")
+ return None
+ ok, version = node_ok(node)
+ if not ok:
+ log(f"Node.js {version} is too old (need >= {'.'.join(map(str, MIN_NODE))}); starting the dashboard only")
+ return None
+ if not ensure_gateway_deps():
+ log("voice gateway dependencies missing; starting the dashboard only")
+ return None
+ return [node, str(GATEWAY_DIR / "src" / "index.js")]
+
+
+def main():
+ web = CONFIG["web"]
+ gateway_cmd = gateway_command()
+ env = dict(os.environ)
+
+ if gateway_cmd:
+ env.update(DASHBOARD_HOST="127.0.0.1", DASHBOARD_LISTEN_PORT=str(web["dashboard_port"]))
+ else:
+ env.update(DASHBOARD_HOST=str(web["host"]), DASHBOARD_LISTEN_PORT=str(web["port"]))
+
+ procs = [("dashboard", subprocess.Popen([sys.executable, str(BASE_DIR / "app.py")], cwd=BASE_DIR, env=env))]
+ if gateway_cmd:
+ procs.append(("gateway", subprocess.Popen(gateway_cmd, cwd=BASE_DIR, env=env)))
+ log(f"open http://{'localhost' if web['host'] in ('0.0.0.0', '::') else web['host']}:{web['port']}")
+
+ stopping = False
+
+ def stop(*_):
+ nonlocal stopping
+ if stopping:
+ return
+ stopping = True
+ for _, proc in procs:
+ if proc.poll() is None:
+ proc.send_signal(signal.SIGTERM)
+
+ signal.signal(signal.SIGINT, stop)
+ signal.signal(signal.SIGTERM, stop)
+
+ exit_code = 0
+ try:
+ while not stopping:
+ for name, proc in procs:
+ code = proc.poll()
+ if code is not None:
+ log(f"{name} exited with code {code}; shutting everything down")
+ exit_code = code or 1
+ stop()
+ break
+ time.sleep(0.5)
+ finally:
+ stop()
+ deadline = time.time() + 5
+ for _, proc in procs:
+ try:
+ proc.wait(timeout=max(0.1, deadline - time.time()))
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ sys.exit(exit_code)
+
+
+if __name__ == "__main__":
+ main()
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage