Spaces:
Running on Zero
Running on Zero
File size: 8,730 Bytes
32b00f5 cd2ba4f 32b00f5 8a43cd2 32b00f5 8a43cd2 32b00f5 8a43cd2 32b00f5 8a43cd2 32b00f5 cd2ba4f 0434636 cd2ba4f fabc3f1 1c39ce7 fabc3f1 8a43cd2 fabc3f1 8a43cd2 32b00f5 8a43cd2 32b00f5 fabc3f1 8a43cd2 fabc3f1 8a43cd2 fabc3f1 8a43cd2 32b00f5 8a43cd2 32b00f5 8a43cd2 32b00f5 8a43cd2 32b00f5 8a43cd2 32b00f5 8a43cd2 cd2ba4f 91cdc25 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | """Public Hugging Face ZeroGPU Gateway for PropNode Admin.
- Keeps the real codebase private in GitHub (`Gini-Eddie/propnode-admin`).
- Displays a public decoy Gradio interface at `/` to regular Hugging Face visitors.
- Unlocks the real PropNode Admin portal only via `/gate/<GATEWAY_KEY>` or authorized origins.
ZeroGPU only registers @spaces.GPU functions when the app starts through
`demo.launch()`. The gateway is therefore attached as ASGI middleware on Gradio's
own server (via `app_kwargs`), instead of running a separate Uvicorn server.
"""
import hashlib
import hmac
import logging
import os
import shutil
import subprocess
import sys
from http.cookies import SimpleCookie
from pathlib import Path
import huggingface_hub
# Compatibility shim if huggingface_hub >= 1.0 is present in the base container
if not hasattr(huggingface_hub, "HfFolder"):
class _HfFolder:
@classmethod
def get_token(cls):
return os.environ.get("HF_TOKEN")
@classmethod
def save_token(cls, token: str):
pass
huggingface_hub.HfFolder = _HfFolder
import gradio as gr
import spaces
import torch
from starlette.middleware import Middleware
from starlette.responses import RedirectResponse
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("propnode.gateway")
# ---------------------------------------------------------------------------
# 1. ZeroGPU Decoy Interface (shown to public Hugging Face visitors)
# ---------------------------------------------------------------------------
zero = torch.Tensor([0]).cuda()
@spaces.GPU
def greet(n: float) -> str:
return f"Node telemetry ping: {zero.item() + float(n or 0):.1f} OK"
demo = gr.Interface(
fn=greet,
inputs=gr.Number(label="Diagnostic Signal", value=1),
outputs=gr.Text(label="Gateway Response"),
title="PropNode Telemetry Gateway",
description="Internal compute node status check.",
)
# ---------------------------------------------------------------------------
# 2. Private Repository Loader
# ---------------------------------------------------------------------------
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "").strip()
GITHUB_REPO = os.environ.get("GITHUB_REPO", "Gini-Eddie/propnode-admin").strip()
GATEWAY_KEY = os.environ.get("GATEWAY_KEY", "").strip()
RUNTIME_DIR = Path("/tmp/propnode_admin_runtime")
GATE_COOKIE = "__pn_gate"
def _expected_key() -> str:
return GATEWAY_KEY or os.environ.get("ADMIN_SECRET_KEY", "").strip()
def _gate_token() -> str:
secret = _expected_key() or "fallback-unconfigured"
return hashlib.sha256(f"propnode-gate:{secret}".encode()).hexdigest()
def _load_private_admin_app():
missing = [
name
for name in ("GITHUB_TOKEN", "DATABASE_URL", "ADMIN_SECRET_KEY")
if not os.environ.get(name, "").strip()
]
if missing:
logger.warning(
"Gateway running in decoy-only mode. Configure Space Secrets to enable backend: %s",
", ".join(missing),
)
return None
if RUNTIME_DIR.exists():
shutil.rmtree(RUNTIME_DIR)
repo_url = f"https://x-access-token:{GITHUB_TOKEN}@github.com/{GITHUB_REPO}.git"
logger.info("Fetching private repository %s into runtime directory...", GITHUB_REPO)
result = subprocess.run(
["git", "clone", "--depth", "1", repo_url, str(RUNTIME_DIR)],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
# stderr may contain the token-bearing URL; do not log it verbatim.
logger.error("Failed to clone private repository (code %s).", result.returncode)
return None
sys.path.insert(0, str(RUNTIME_DIR))
try:
from backend.app.main import app as private_app
logger.info("Private PropNode Admin backend mounted successfully.")
return private_app
except Exception as exc:
logger.error("Failed to import private backend: %s", exc)
return None
admin_app = _load_private_admin_app()
# ---------------------------------------------------------------------------
# 3. Gateway Middleware (runs in front of Gradio's own server)
# ---------------------------------------------------------------------------
GRADIO_PREFIXES = (
"/gradio_api",
"/queue/",
"/config",
"/info",
"/theme.css",
"/custom.css",
"/file=",
"/heartbeat/",
"/manifest.json",
)
def _is_gradio_route(path: str) -> bool:
if path.startswith(GRADIO_PREFIXES):
return True
if path.startswith("/assets/"):
# Route `/assets/logo.png` and `/assets/fonts/*` to PropNode Admin;
# route Gradio's own hashed JS/CSS bundle files in `/assets/` to Gradio.
if path in {"/assets/logo.png", "/assets/icon-logo.png"} or path.startswith("/assets/fonts/"):
return False
return True
return False
def _headers(scope: dict) -> dict:
return {k.decode("latin1").lower(): v.decode("latin1") for k, v in scope.get("headers", [])}
def _is_authorized(scope: dict) -> bool:
if admin_app is None or scope.get("type") != "http":
return False
path: str = scope.get("path", "")
if _is_gradio_route(path):
return False
headers = _headers(scope)
# Never expose the admin app inside the Hugging Face Space iframe
if headers.get("sec-fetch-dest") == "iframe":
return False
# 1. Browser gateway cookie set via /gate/<GATEWAY_KEY> or active admin session cookie
raw_cookie = headers.get("cookie", "")
if raw_cookie:
cookie = SimpleCookie()
try:
cookie.load(raw_cookie)
if GATE_COOKIE in cookie and hmac.compare_digest(cookie[GATE_COOKIE].value, _gate_token()):
return True
if path.startswith("/api/") and "propnode_admin_session" in cookie:
return True
except Exception:
pass
# 2. Explicit X-Gateway-Key header (from a reverse proxy)
expected = _expected_key()
header_key = headers.get("x-gateway-key", "")
if expected and header_key and hmac.compare_digest(header_key, expected):
return True
# 3. API call from an explicitly allowed external domain (e.g. https://admin.propnode.app)
allowed_origins = {
o.strip().rstrip("/")
for o in os.environ.get("ADMIN_ALLOWED_ORIGINS", "").split(",")
if o.strip() and not o.strip().endswith(".hf.space")
}
if path.startswith("/api/") and allowed_origins:
origin = headers.get("origin", "").rstrip("/")
referer = headers.get("referer", "")
forwarded_host = headers.get("x-forwarded-host", "").strip()
if origin and origin in allowed_origins:
return True
if any(referer.startswith(o + "/") or referer == o for o in allowed_origins):
return True
if forwarded_host and f"https://{forwarded_host}" in allowed_origins:
return True
return False
class GatewayMiddleware:
"""Pure ASGI middleware: routes authorized traffic to PropNode Admin, the rest to Gradio."""
def __init__(self, app):
self.app = app # Gradio's ASGI app
async def __call__(self, scope, receive, send):
if scope.get("type") == "http" and scope.get("path", "").startswith("/gate/"):
key = scope["path"][len("/gate/"):].strip("/")
expected = _expected_key()
if admin_app is None or not expected or not hmac.compare_digest(key, expected):
await RedirectResponse(url="/", status_code=302)(scope, receive, send)
return
response = RedirectResponse(url="/login.html", status_code=302)
response.set_cookie(
GATE_COOKIE,
_gate_token(),
httponly=True,
secure=True,
samesite="lax",
path="/",
max_age=86400 * 30,
)
await response(scope, receive, send)
return
if _is_authorized(scope):
await admin_app(scope, receive, send)
return
await self.app(scope, receive, send)
# ---------------------------------------------------------------------------
# 4. Launch through Gradio so ZeroGPU detects the @spaces.GPU function
# ---------------------------------------------------------------------------
if __name__ == "__main__":
demo.launch(
server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
server_port=int(os.environ.get("GRADIO_SERVER_PORT", 7860)),
ssr_mode=False,
app_kwargs={"middleware": [Middleware(GatewayMiddleware)]},
)
# Deployed: 2026-11-08 21:24:00 UTC
|