"""Webhook-сервер для автодеплоя из Gitea (push → redeploy).""" from __future__ import annotations import asyncio import json from typing import Optional from sqlmodel import select from app.core import servers as servers_core from app.db.database import get_session from app.db.models import WebhookBinding _server = None def list_bindings() -> list[WebhookBinding]: with get_session() as s: return list(s.exec(select(WebhookBinding)).all()) def add_binding(**fields) -> WebhookBinding: with get_session() as s: b = WebhookBinding(**fields) s.add(b) s.commit() s.refresh(b) return b def delete_binding(bid: int) -> None: with get_session() as s: b = s.get(WebhookBinding, bid) if b: s.delete(b) s.commit() async def _handler(reader, writer): """Минимальный HTTP-сервер для /webhooks/gitea.""" try: req = await reader.readuntil(b"\r\n\r\n") head = req.decode(errors="replace") first = head.splitlines()[0] if head else "" # тело length = 0 for line in head.splitlines(): if line.lower().startswith("content-length:"): length = int(line.split(":", 1)[1].strip()) body = await reader.readexactly(length) if length else b"" status, text = 200, "ok" if first.startswith("POST /webhooks/gitea"): try: payload = json.loads(body or b"{}") repo = payload.get("repository", {}).get("full_name", "") ref = payload.get("ref", "") # refs/heads/main branch = ref.rsplit("/", 1)[-1] asyncio.ensure_future(_deploy(repo, branch)) text = f"deploy scheduled: {repo}@{branch}" except Exception as e: status, text = 400, f"bad payload: {e}" else: status, text = 404, "not found" writer.write(f"HTTP/1.1 {status} OK\r\nContent-Length: {len(text)}\r\n" f"Connection: close\r\n\r\n{text}".encode()) await writer.drain() except Exception: pass finally: try: writer.close() except Exception: pass async def _deploy(repo: str, branch: str) -> None: with get_session() as s: bindings = list(s.exec(select(WebhookBinding) .where(WebhookBinding.repo == repo, WebhookBinding.branch == branch, WebhookBinding.enabled == True)).all()) # noqa: E712 for b in bindings: srv = servers_core.get_server(b.server_id) if not srv: continue try: from app.core import gitea as g from app.core.ssh import SSHClient from app.core.tasks import tasks async def job(log, srv=srv, b=b): async with SSHClient(srv) as c: cfg = g.get_config() clone_url = f"{cfg['url']}/{repo}.git" await g.deploy(c, clone_url, repo.split("/")[-1], b.branch, b.compose_path, b.env or None, log) tasks.start("webhook", f"auto-deploy {repo}@{branch}", srv.id, job) except Exception: pass async def start(port: int = 9100) -> None: global _server if _server: return _server = await asyncio.start_server(_handler, "0.0.0.0", port) def is_running() -> bool: return _server is not None