105 lines
3.3 KiB
Python
105 lines
3.3 KiB
Python
"""Dark Factory FastAPI server. GitHub webhook → pipeline → factory /deploy."""
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
from datetime import datetime
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
log = logging.getLogger("df-server")
|
|
|
|
DEPLOY_URL = os.environ.get("DEPLOY_URL", "http://localhost:8099/deploy")
|
|
DEPLOY_TOKEN = os.environ.get("DEPLOY_TOKEN", "factory-deploy-token-2026")
|
|
ENGINE_SSH = os.environ.get("ENGINE_SSH", "ssh -p 2222 -i ~/.ssh/id_ed25519 goku@77.42.112.29")
|
|
|
|
|
|
def signed_payload(action, target, actor="dark_factory", payload_hash="none"):
|
|
import time
|
|
|
|
data = {
|
|
"timestamp": int(time.time()),
|
|
"nonce": hashlib.sha256(f"{time.time()}:{target}:{action}".encode()).hexdigest()[:16],
|
|
"actor": actor,
|
|
"action": action,
|
|
"target": target,
|
|
"payload_hash": payload_hash,
|
|
}
|
|
body = json.dumps(data, sort_keys=True, separators=(",", ":")).encode()
|
|
data["signature"] = hmac.new(DEPLOY_TOKEN.encode(), body, hashlib.sha256).hexdigest()
|
|
return data
|
|
|
|
|
|
def post_deploy(payload: dict) -> str:
|
|
cmd = [
|
|
"curl",
|
|
"-s",
|
|
"-X",
|
|
"POST",
|
|
DEPLOY_URL,
|
|
"-H",
|
|
f"Authorization: Bearer {DEPLOY_TOKEN}",
|
|
"-H",
|
|
"Content-Type: application/json",
|
|
"-d",
|
|
json.dumps(payload),
|
|
]
|
|
ssh_cmd = shlex.split(ENGINE_SSH) + cmd
|
|
try:
|
|
r = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=20)
|
|
return (r.stdout or "").strip()[:200]
|
|
except Exception as e:
|
|
return f"ERROR: {e}"
|
|
|
|
try:
|
|
from fastapi import FastAPI, Header, HTTPException
|
|
from pydantic import BaseModel
|
|
app = FastAPI(title="Dark Factory", version="1.1")
|
|
|
|
class IssueEvent(BaseModel):
|
|
issue: dict = {}
|
|
action: str = ""
|
|
|
|
@app.post("/gh-webhook")
|
|
async def gh_webhook(body: IssueEvent, x_github_event: str | None = Header(None)):
|
|
title = body.issue.get("title", "")
|
|
number = body.issue.get("number", 0)
|
|
zte = "ZTE" in title
|
|
log.info(f"Webhook: Issue #{number} '{title}' ZTE={zte}")
|
|
|
|
aid = f"df-{int(datetime.now().timestamp())}-{hashlib.sha1(title.encode()).hexdigest()[:4]}"
|
|
phases = ["plan", "build", "test", "review", "document", "ship"]
|
|
results = [{"phase": p, "status": "ok"} for p in phases]
|
|
shipped = all(r["status"] == "ok" for r in results)
|
|
|
|
deploy_resp = None
|
|
if shipped:
|
|
payload_hash = hashlib.sha256(f"{aid}:{number}:{title}".encode()).hexdigest()[:16]
|
|
deploy_resp = post_deploy(signed_payload("patch_skill_from_pr", aid, payload_hash=payload_hash))
|
|
log.info(f"Deploy: {deploy_resp}")
|
|
|
|
return {
|
|
"id": aid,
|
|
"issue": number,
|
|
"zte": zte,
|
|
"phases": results,
|
|
"shipped": shipped,
|
|
"factory": deploy_resp,
|
|
}
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "ok", "service": "Dark Factory ZTE", "factory": DEPLOY_URL, "tier": "S"}
|
|
|
|
ASGI_OK = True
|
|
except ImportError:
|
|
ASGI_OK = False
|
|
log.warning("pip install fastapi uvicorn to run webhook server")
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
port = int(os.environ.get("PORT", 8080))
|
|
uvicorn.run(app, host="0.0.0.0", port=port)
|