import os, hmac, hashlib, subprocess, threading from fastapi import FastAPI, Request, HTTPException, BackgroundTasks import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S %Z", ) logger = logging.getLogger("deployer") app = FastAPI() SECRET = os.getenv("WEBHOOK_SECRET", "") if not SECRET: logger.warning("WEBHOOK_SECRET is not set! All webhooks will be rejected.") _deploy_lock = threading.Lock() def verify(sig: str, body: bytes) -> bool: if not SECRET or not sig: return False mac = hmac.new(SECRET.encode(), msg=body, digestmod=hashlib.sha256).hexdigest() candidates = {mac, f"sha256={mac}"} return any(hmac.compare_digest(sig, c) for c in candidates) def run_deploy_script(): """배포 스크립트를 백그라운드에서 실행 (동시 실행 방지)""" if not _deploy_lock.acquire(blocking=False): logger.info("Deploy already in progress, skipping") return try: logger.info("Starting deployment script...") p = subprocess.run(["/bin/bash", "/scripts/deploy.sh"], capture_output=True, text=True, timeout=600) if p.returncode == 0: logger.info(f"Deployment SUCCESS:\n{p.stdout}") else: logger.error(f"Deployment FAILED ({p.returncode}):\n{p.stdout}\n{p.stderr}") except subprocess.TimeoutExpired: logger.error("Deployment TIMEOUT (10 min exceeded)") except Exception as e: logger.exception(f"Exception during deployment: {e}") finally: _deploy_lock.release() @app.get("/health") def health(): return {"status": "healthy", "service": "deployer"} @app.post("/webhook") async def webhook(req: Request, background_tasks: BackgroundTasks): body = await req.body() sig = ( req.headers.get("X-Gitea-Signature") or req.headers.get("X-Hub-Signature-256") or "" ) if not verify(sig, body): raise HTTPException(401, "bad signature") # ✅ 비동기 실행: Gitea에게는 즉시 OK 응답을 주고, 배포는 뒤에서 실행 background_tasks.add_task(run_deploy_script) return {"ok": True, "message": "Deployment started in background"}