From 6bad950f941c506e3ee8dbe2472d04dd98dca095 Mon Sep 17 00:00:00 2001 From: filip-rs Date: Wed, 8 Apr 2026 00:43:23 +0200 Subject: [PATCH 1/2] Fixed so this runs on linux with active WM --- .gitignore | 3 +++ service.py | 16 ++++++++++++---- solver.py | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32ac21b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv +.env +__pycache__ diff --git a/service.py b/service.py index 21b7e1a..34e65d7 100644 --- a/service.py +++ b/service.py @@ -45,19 +45,27 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): def _ensure_display() -> Optional[subprocess.Popen]: - """On Linux headless servers, start a virtual display so Chrome can run.""" + """Start a virtual Xvfb display so Chrome runs invisibly. + + Forces Chrome onto X11 via Xvfb even on Wayland desktops: + - Unsets WAYLAND_DISPLAY so Chrome can't connect to the compositor + - Sets DISPLAY to the virtual Xvfb server + """ if platform.system() != "Linux": return None - if os.environ.get("DISPLAY"): - return None + # Kill any stale Xvfb on :99 + subprocess.run(["pkill", "-f", "Xvfb :99"], stderr=subprocess.DEVNULL) + time.sleep(0.3) xvfb = subprocess.Popen( ["Xvfb", ":99", "-screen", "0", "1280x900x24"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) + # Force X11 — prevent Chrome from connecting to Wayland/Hyprland + os.environ.pop("WAYLAND_DISPLAY", None) os.environ["DISPLAY"] = ":99" time.sleep(0.5) - print("[service] started Xvfb on :99") + print("[service] started Xvfb on :99 (Wayland disabled, Chrome renders offscreen)") return xvfb diff --git a/solver.py b/solver.py index bc97835..96e206f 100644 --- a/solver.py +++ b/solver.py @@ -73,6 +73,7 @@ async def _solve(sitekey: str, siteurl: str, timeout: int) -> str: browser_executable_path=_find_chrome(), headless=False, user_data_dir=_get_profile_dir(), + browser_args=["--ozone-platform=x11"], ) try: From 5224cf01b1775aa6881beedcf5f9789a9aab2aa6 Mon Sep 17 00:00:00 2001 From: filip-rs Date: Wed, 8 Apr 2026 00:49:36 +0200 Subject: [PATCH 2/2] cleaned print --- service.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/service.py b/service.py index 34e65d7..868dc41 100644 --- a/service.py +++ b/service.py @@ -12,7 +12,7 @@ made by ismoiloff """ - +import json import os import platform import subprocess @@ -21,11 +21,9 @@ from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import ThreadingMixIn from typing import Optional -import json from solver import solve - PORT = int(os.environ.get("PORT", 8191)) # How many Chrome instances to run in parallel. # Rule of thumb: ~500 MB RAM per worker. 4 workers = ~2 GB. @@ -41,6 +39,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): """Handle each request in its own thread so solves don't block each other.""" + daemon_threads = True @@ -65,7 +64,7 @@ def _ensure_display() -> Optional[subprocess.Popen]: os.environ.pop("WAYLAND_DISPLAY", None) os.environ["DISPLAY"] = ":99" time.sleep(0.5) - print("[service] started Xvfb on :99 (Wayland disabled, Chrome renders offscreen)") + print("[service] started Xvfb on :99") return xvfb @@ -107,8 +106,10 @@ def do_POST(self): with _count_lock: _queued_count += 1 - print(f"[service] queued — sitekey={sitekey!r} url={siteurl!r} " - f"(active={_active_count}/{MAX_WORKERS} queued={_queued_count})") + print( + f"[service] queued — sitekey={sitekey!r} url={siteurl!r} " + f"(active={_active_count}/{MAX_WORKERS} queued={_queued_count})" + ) # Block until a worker slot is free — other threads keep running _worker_sem.acquire() @@ -119,8 +120,10 @@ def do_POST(self): t0 = time.time() try: - print(f"[service] solving sitekey={sitekey!r} url={siteurl!r} " - f"(active={_active_count}/{MAX_WORKERS})") + print( + f"[service] solving sitekey={sitekey!r} url={siteurl!r} " + f"(active={_active_count}/{MAX_WORKERS})" + ) token = solve(sitekey, siteurl, timeout=timeout) elapsed = round(time.time() - t0, 2) print(f"[service] solved in {elapsed}s token={token[:20]}...") @@ -137,12 +140,15 @@ def do_POST(self): def do_GET(self): if self.path == "/health": with _count_lock: - self.send_json(200, { - "status": "ok", - "workers": MAX_WORKERS, - "active": _active_count, - "queued": _queued_count, - }) + self.send_json( + 200, + { + "status": "ok", + "workers": MAX_WORKERS, + "active": _active_count, + "queued": _queued_count, + }, + ) else: self.send_json(404, {"error": "use POST /solve"}) @@ -151,8 +157,10 @@ def do_GET(self): xvfb_proc = _ensure_display() server = ThreadedHTTPServer(("0.0.0.0", PORT), Handler) print(f"[service] Turnstile solver service running on http://0.0.0.0:{PORT}") - print(f"[service] worker pool: {MAX_WORKERS} concurrent Chrome instances " - f"(set MAX_WORKERS env var to change)") + print( + f"[service] worker pool: {MAX_WORKERS} concurrent Chrome instances " + f"(set MAX_WORKERS env var to change)" + ) try: server.serve_forever() except KeyboardInterrupt: