#!/usr/bin/env python3 """Meta API wrapper — starts engine via werkzeug, exposes on port 7860.""" import os import sys import subprocess import time import threading import httpx import uvicorn from fastapi import FastAPI, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, Response app = FastAPI(title="Meta API", docs_url=None, redoc_url=None) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) INTERNAL_PORT = 8888 INTERNAL_URL = f"http://127.0.0.1:{INTERNAL_PORT}" _engine_proc = None _engine_ready = False def start_engine(): """Start internal engine via werkzeug (process = python3, NOT granian/searxng).""" global _engine_proc, _engine_ready os.environ["SEARXNG_SETTINGS_PATH"] = "/etc/searxng/settings.yml" launcher = "/tmp/eng.py" with open(launcher, "w") as lf: lf.write(f""" import os, sys os.environ["SEARXNG_SETTINGS_PATH"] = "/etc/searxng/settings.yml" sys.path.insert(0, "/usr/local/searxng") os.chdir("/usr/local/searxng") from searx.webapp import app from werkzeug.serving import run_simple run_simple("127.0.0.1", {INTERNAL_PORT}, app, threaded=True, use_reloader=False) """) _engine_proc = subprocess.Popen( [sys.executable, launcher], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd="/usr/local/searxng", ) print(f"Engine starting (pid={_engine_proc.pid})...", flush=True) for i in range(120): if _engine_proc.poll() is not None: stderr = _engine_proc.stderr.read().decode()[-500:] print(f"Engine died: {stderr}", flush=True) return try: resp = httpx.get(f"{INTERNAL_URL}/", timeout=2.0) if resp.status_code == 200: _engine_ready = True print(f"Engine ready after {i+1}s", flush=True) return except Exception: pass time.sleep(1) print("Engine timeout after 120s", flush=True) @app.on_event("startup") async def startup(): threading.Thread(target=start_engine, daemon=True).start() @app.on_event("shutdown") async def shutdown(): if _engine_proc: _engine_proc.terminate() @app.get("/") async def root(): return {"status": "ok", "service": "meta-api", "version": "6.0.0", "engine": "ready" if _engine_ready else "starting"} @app.get("/health") async def health(): return {"status": "healthy" if _engine_ready else "starting", "engine": "ready" if _engine_ready else "initializing"} @app.get("/search") async def search( q: str = Query(...), format: str = Query("json"), categories: str = Query("general"), language: str = Query("en"), time_range: str = Query(None), pageno: int = Query(1), ): if not _engine_ready: return JSONResponse(status_code=503, content={"error": "Engine still starting", "results": []}) params = {"q": q, "format": "json", "categories": categories, "language": language, "pageno": pageno} if time_range: params["time_range"] = time_range try: async with httpx.AsyncClient(timeout=20.0) as client: resp = await client.get(f"{INTERNAL_URL}/search", params=params) if resp.status_code == 200: return JSONResponse(content=resp.json()) return JSONResponse(status_code=resp.status_code, content={"error": f"Engine {resp.status_code}", "results": []}) except Exception as e: return JSONResponse(status_code=503, content={"error": str(e), "results": []}) if __name__ == "__main__": port = int(os.environ.get("GRANIAN_PORT", os.environ.get("PORT", 7860))) uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")