67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
import asyncio
|
|
import hashlib
|
|
import hmac
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
|
|
from config import settings
|
|
|
|
logger = logging.getLogger("admin.deploy")
|
|
|
|
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
|
|
|
|
|
@router.post("/deploy")
|
|
async def deploy(request: Request):
|
|
"""Gitea webhook endpoint — pulls latest code and rebuilds Docker containers.
|
|
|
|
Gitea webhook configuration:
|
|
URL: https://<your-domain>/api/admin/deploy
|
|
Secret token: value of DEPLOY_SECRET env var
|
|
Content-Type: application/json
|
|
Trigger: Push events only (branch: main)
|
|
|
|
Add to VPS .env:
|
|
DEPLOY_SECRET=<random-strong-token>
|
|
DEPLOY_PROJECT_PATH=/home/bellsystems/bellsystems-cp
|
|
"""
|
|
if not settings.deploy_secret:
|
|
raise HTTPException(status_code=503, detail="Deploy secret not configured on server")
|
|
|
|
# Gitea sends the HMAC-SHA256 of the request body in X-Gitea-Signature
|
|
sig_header = request.headers.get("X-Gitea-Signature", "")
|
|
body = await request.body()
|
|
expected_sig = hmac.new(
|
|
key=settings.deploy_secret.encode(),
|
|
msg=body,
|
|
digestmod=hashlib.sha256,
|
|
).hexdigest()
|
|
if not hmac.compare_digest(sig_header, expected_sig):
|
|
raise HTTPException(status_code=403, detail="Invalid webhook signature")
|
|
|
|
logger.info("Auto-deploy triggered via Gitea webhook")
|
|
|
|
project_path = settings.deploy_project_path
|
|
cmd = (
|
|
f"sleep 3 && "
|
|
f"git config --global --add safe.directory {project_path} && "
|
|
f"cd {project_path} && "
|
|
f"git fetch origin main && "
|
|
f"git reset --hard origin/main && "
|
|
f"docker-compose up -d --build"
|
|
f" > /proc/1/fd/1 2>&1"
|
|
)
|
|
|
|
# Fire and forget — sleep gives uvicorn time to flush the HTTP response
|
|
# before docker-compose tears down and restarts this container.
|
|
await asyncio.create_subprocess_shell(
|
|
cmd,
|
|
stdout=asyncio.subprocess.DEVNULL,
|
|
stderr=asyncio.subprocess.DEVNULL,
|
|
start_new_session=True,
|
|
)
|
|
|
|
logger.info("Auto-deploy queued (starts in 3s)")
|
|
return {"ok": True, "message": "Deploy started"}
|