Initial commit: Debian Manager — управление Debian-серверами
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user