Initial commit: site monorepo with API, web, and infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pair local WESP hub with site orchestrator (Docker :8000) and write hub state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import secrets
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
LOG_PATH = Path("/Users/vlad/Documents/wesp new (1)/.cursor/debug-785e22.log")
|
||||
SESSION = "785e22"
|
||||
RUN_ID = "pair-v1"
|
||||
ORCH_URL = "http://127.0.0.1:8000"
|
||||
WESP_STATE = Path(__file__).resolve().parents[2] / "wesp/data/orchestrator_sync_state.json"
|
||||
ADMIN_EMAIL = "admin@compton.example"
|
||||
ADMIN_PASSWORD = "Admin1234"
|
||||
|
||||
|
||||
def _log(hypothesis_id: str, location: str, message: str, data: dict) -> None:
|
||||
payload = {
|
||||
"sessionId": SESSION,
|
||||
"runId": RUN_ID,
|
||||
"hypothesisId": hypothesis_id,
|
||||
"location": location,
|
||||
"message": message,
|
||||
"data": data,
|
||||
"timestamp": int(time.time() * 1000),
|
||||
}
|
||||
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with LOG_PATH.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
||||
|
||||
|
||||
def _request(method: str, url: str, body: dict | None = None, headers: dict | None = None) -> tuple[int, dict | list | str]:
|
||||
data = None if body is None else json.dumps(body).encode("utf-8")
|
||||
hdrs = {"Content-Type": "application/json", **(headers or {})}
|
||||
req = urllib.request.Request(url, data=data, headers=hdrs, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
raw = resp.read(4000).decode("utf-8", errors="replace")
|
||||
try:
|
||||
return resp.status, json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return resp.status, raw
|
||||
except urllib.error.HTTPError as exc:
|
||||
raw = exc.read(4000).decode("utf-8", errors="replace")
|
||||
try:
|
||||
return exc.code, json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return exc.code, raw
|
||||
|
||||
|
||||
def _login() -> str:
|
||||
code, body = _request(
|
||||
"POST",
|
||||
f"{ORCH_URL}/api/v1/auth/login",
|
||||
{"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD},
|
||||
{"Origin": "http://localhost:5173"},
|
||||
)
|
||||
if code != 200 or not isinstance(body, dict) or not body.get("access_token"):
|
||||
raise RuntimeError(f"login failed: {code} {body}")
|
||||
return str(body["access_token"])
|
||||
|
||||
|
||||
def _ensure_enterprise(token: str) -> str:
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
code, body = _request("GET", f"{ORCH_URL}/api/v1/enterprise/enterprises", headers=auth)
|
||||
if code != 200:
|
||||
raise RuntimeError(f"list enterprises failed: {code} {body}")
|
||||
if isinstance(body, list) and body:
|
||||
ent_id = str(body[0]["id"])
|
||||
_log("H3", "pair:list-enterprises", "using existing enterprise", {"enterprise_id": ent_id, "name": body[0].get("name")})
|
||||
return ent_id
|
||||
slug = f"local-dev-{secrets.token_hex(4)}"
|
||||
code, created = _request(
|
||||
"POST",
|
||||
f"{ORCH_URL}/api/v1/enterprise/enterprises",
|
||||
{"name": "Local Dev Farm", "slug": slug},
|
||||
auth,
|
||||
)
|
||||
if code not in (200, 201) or not isinstance(created, dict):
|
||||
raise RuntimeError(f"create enterprise failed: {code} {created}")
|
||||
ent_id = str(created["id"])
|
||||
_log("H3", "pair:create-enterprise", "created enterprise", {"enterprise_id": ent_id, "slug": slug})
|
||||
return ent_id
|
||||
|
||||
|
||||
def _pair_hub(token: str, enterprise_id: str, hub_site_id: str, hub_name: str) -> dict:
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
code, start = _request(
|
||||
"POST",
|
||||
f"{ORCH_URL}/api/v1/enterprise/pair/start",
|
||||
{"enterprise_id": enterprise_id, "farm_name": hub_name},
|
||||
auth,
|
||||
)
|
||||
if code != 200 or not isinstance(start, dict) or not start.get("code"):
|
||||
raise RuntimeError(f"pair start failed: {code} {start}")
|
||||
pair_code = str(start["code"])
|
||||
code, confirm = _request(
|
||||
"POST",
|
||||
f"{ORCH_URL}/api/v1/enterprise/pair/confirm",
|
||||
{"code": pair_code, "hub_site_id": hub_site_id, "hub_name": hub_name, "hub_url": "http://127.0.0.1/"},
|
||||
)
|
||||
if code != 200 or not isinstance(confirm, dict) or not confirm.get("api_key"):
|
||||
raise RuntimeError(f"pair confirm failed: {code} {confirm}")
|
||||
_log(
|
||||
"H3",
|
||||
"pair:confirm",
|
||||
"hub paired with orchestrator",
|
||||
{
|
||||
"enterprise_id": confirm.get("enterprise_id"),
|
||||
"hub_site_id": confirm.get("hub_site_id"),
|
||||
"farm_hub_id": confirm.get("farm_hub_id"),
|
||||
},
|
||||
)
|
||||
return confirm
|
||||
|
||||
|
||||
def _write_wesp_state(confirm: dict) -> None:
|
||||
state = {
|
||||
"upstream_url": ORCH_URL,
|
||||
"api_key": confirm["api_key"],
|
||||
"hub_site_id": confirm["hub_site_id"],
|
||||
"enterprise_id": confirm["enterprise_id"],
|
||||
"enabled": True,
|
||||
"catalog_bootstrap_done": False,
|
||||
"reports_bootstrap_done": False,
|
||||
"pull_cursor": 0,
|
||||
}
|
||||
WESP_STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
WESP_STATE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
_log("H3", "pair:write-state", "wrote orchestrator_sync_state.json", {"path": str(WESP_STATE), "enabled": True})
|
||||
|
||||
|
||||
def _verify_hub_api() -> None:
|
||||
code, body = _request("GET", f"{ORCH_URL}/api/v1/health")
|
||||
_log("H5", "pair:orch-health", "orchestrator health after pair", {"status": code})
|
||||
|
||||
state = json.loads(WESP_STATE.read_text(encoding="utf-8"))
|
||||
hub_site_id = state["hub_site_id"]
|
||||
api_key = state["api_key"]
|
||||
auth = {"Authorization": f"Hub {hub_site_id}:{api_key}"}
|
||||
code, conflicts = _request("GET", f"{ORCH_URL}/api/v1/sync/hub/conflicts", headers=auth)
|
||||
_log(
|
||||
"H5",
|
||||
"pair:hub-conflicts",
|
||||
"hub-auth conflicts list",
|
||||
{"status": code, "count": len(conflicts) if isinstance(conflicts, list) else None, "body": conflicts if code != 200 else None},
|
||||
)
|
||||
|
||||
|
||||
def _run_hub_bootstrap() -> None:
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
wesp_root = WESP_STATE.parents[1]
|
||||
script = """
|
||||
from app import create_app
|
||||
from app.services.orchestrator_sync.engine import OrchestratorSyncEngine
|
||||
from config import Config
|
||||
|
||||
app = create_app(Config)
|
||||
with app.app_context():
|
||||
engine = OrchestratorSyncEngine()
|
||||
catalog_created = engine.bootstrap_catalog_if_needed()
|
||||
reports_created = engine.bootstrap_reports_if_needed()
|
||||
print(f"bootstrap_catalog={catalog_created} bootstrap_reports={reports_created}")
|
||||
for _ in range(30):
|
||||
engine.run_cycle()
|
||||
"""
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
cwd=str(wesp_root),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
_log(
|
||||
"H1",
|
||||
"pair:bootstrap",
|
||||
"hub catalog bootstrap after pair",
|
||||
{
|
||||
"returncode": proc.returncode,
|
||||
"stdout": (proc.stdout or "")[-500:],
|
||||
"stderr": (proc.stderr or "")[-500:],
|
||||
},
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
print(
|
||||
"Warning: hub catalog bootstrap subprocess exited non-zero; "
|
||||
"pending outbox rows will sync on the next hub run.py cycle.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def _repair_orchestrator_report_payloads(enterprise_id: str) -> None:
|
||||
import subprocess
|
||||
|
||||
script = f"""
|
||||
from app.modules.zootech.report_apply import repair_report_payloads_from_event_log
|
||||
repaired = repair_report_payloads_from_event_log({enterprise_id!r})
|
||||
print(f"repair_report_payloads={{repaired}}")
|
||||
"""
|
||||
proc = subprocess.run(
|
||||
["docker", "exec", "site-api-1", "python3", "-c", script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
_log(
|
||||
"H2",
|
||||
"pair:repair-reports",
|
||||
"orchestrator report payload repair from event log",
|
||||
{
|
||||
"returncode": proc.returncode,
|
||||
"stdout": (proc.stdout or "")[-300:],
|
||||
"stderr": (proc.stderr or "")[-300:],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
hub_site_id = f"local-wesp-{secrets.token_hex(4)}"
|
||||
token = _login()
|
||||
enterprise_id = _ensure_enterprise(token)
|
||||
confirm = _pair_hub(token, enterprise_id, hub_site_id, "Local WESP Hub")
|
||||
_write_wesp_state(confirm)
|
||||
_run_hub_bootstrap()
|
||||
_repair_orchestrator_report_payloads(confirm["enterprise_id"])
|
||||
_verify_hub_api()
|
||||
print("Paired local WESP hub with orchestrator.")
|
||||
print(f" enterprise_id: {confirm['enterprise_id']}")
|
||||
print(f" hub_site_id: {confirm['hub_site_id']}")
|
||||
print(f" state file: {WESP_STATE}")
|
||||
print("Restart WESP run.py if it was already running, then open Multiserver panel.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user