Spaces:
Sleeping
Sleeping
| """HTTP entrypoint for the Cloudflare Container runner. | |
| The Worker creates a queued job in D1, then calls this container's /run-once | |
| endpoint. The container reuses cloudflare_runner.run_once(), so the actual | |
| registration flow stays in Python and runs inside Cloudflare Containers rather | |
| than on the operator's local machine. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import hmac | |
| import os | |
| import tempfile | |
| import threading | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| from pathlib import Path | |
| from types import SimpleNamespace | |
| from typing import Any | |
| from chatgpt_register_sub2api.runner.cloudflare_runner import run_once | |
| _RUN_LOCK = threading.Lock() | |
| class ContainerRunnerHandler(BaseHTTPRequestHandler): | |
| server_version = "ChatGPTRegisterCloudRunner/0.1" | |
| def do_GET(self) -> None: # noqa: N802 - http.server hook | |
| if self.path in {"/", "/health", "/ping"}: | |
| self._json({"ok": True, "service": "registration-runner"}) | |
| return | |
| self._json({"error": "not_found"}, status=404) | |
| def do_POST(self) -> None: # noqa: N802 - http.server hook | |
| if self.path != "/run-once": | |
| self._json({"error": "not_found"}, status=404) | |
| return | |
| if not self._authorized(): | |
| self._json({"ok": False, "error": "unauthorized"}, status=401) | |
| return | |
| if not _RUN_LOCK.acquire(blocking=False): | |
| self._json({"ok": False, "busy": True, "error": "runner_busy"}, status=409) | |
| return | |
| try: | |
| body = self._read_json() | |
| result = self._run_once(body) | |
| self._json(result) | |
| except Exception as error: # keep HTTP service alive after one failed job | |
| self._json({"ok": False, "error": str(error)}, status=500) | |
| finally: | |
| _RUN_LOCK.release() | |
| def log_message(self, fmt: str, *args: Any) -> None: | |
| print("[container-runner] " + (fmt % args), flush=True) | |
| def _run_once(self, body: dict[str, Any]) -> dict[str, Any]: | |
| base_url = str(body.get("base_url") or os.environ.get("WORKER_BASE_URL") or "").strip() | |
| runner_token = str(body.get("runner_token") or os.environ.get("RUNNER_TOKEN") or "").strip() | |
| config_yaml = str(body.get("config_yaml") or os.environ.get("RUNNER_CONFIG_YAML") or "").strip() | |
| timeout = float(body.get("timeout") or os.environ.get("RUNNER_TIMEOUT") or 30) | |
| if not base_url: | |
| raise ValueError("base_url is required") | |
| if not runner_token: | |
| raise ValueError("runner_token is required") | |
| with tempfile.TemporaryDirectory(prefix="cf-container-runner-") as temp_dir: | |
| temp = Path(temp_dir) | |
| config_path = temp / "config.yaml" | |
| if config_yaml: | |
| config_path.write_text(config_yaml, encoding="utf-8") | |
| else: | |
| config_path.write_text("mail:\n providers: []\n", encoding="utf-8") | |
| args = SimpleNamespace( | |
| base_url=base_url, | |
| token=runner_token, | |
| config=str(config_path), | |
| timeout=timeout, | |
| prune_local_config=False, | |
| no_prune_local_backup=True, | |
| ) | |
| did_work = run_once(args) | |
| return {"ok": True, "did_work": did_work} | |
| def _read_json(self) -> dict[str, Any]: | |
| size = int(self.headers.get("content-length") or "0") | |
| if size <= 0: | |
| return {} | |
| raw = self.rfile.read(min(size, 2_000_000)) | |
| data = json.loads(raw.decode("utf-8")) | |
| if not isinstance(data, dict): | |
| raise ValueError("JSON body must be an object") | |
| return data | |
| def _authorized(self) -> bool: | |
| expected = str(os.environ.get("RUNNER_INVOKE_TOKEN") or "").strip() | |
| if not expected: | |
| return True | |
| actual = str(self.headers.get("X-Runner-Invoke-Token") or "").strip() | |
| return hmac.compare_digest(actual, expected) | |
| def _json(self, payload: dict[str, Any], status: int = 200) -> None: | |
| data = json.dumps(payload, ensure_ascii=False).encode("utf-8") | |
| self.send_response(status) | |
| self.send_header("Content-Type", "application/json; charset=utf-8") | |
| self.send_header("Content-Length", str(len(data))) | |
| self.end_headers() | |
| self.wfile.write(data) | |
| def main() -> int: | |
| port = int(os.environ.get("PORT") or "8080") | |
| server = ThreadingHTTPServer(("0.0.0.0", port), ContainerRunnerHandler) | |
| print(f"[container-runner] listening on 0.0.0.0:{port}", flush=True) | |
| server.serve_forever() | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |