123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import os
|
|
import subprocess
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
HOST = os.getenv("WEBHOOK_BIND_HOST", "127.0.0.1")
|
|
PORT = int(os.getenv("WEBHOOK_BIND_PORT", "8788"))
|
|
PATH = os.getenv("WEBHOOK_PATH", "/hooks/gitea/project-velocity")
|
|
SECRET = os.getenv("GITEA_WEBHOOK_SECRET", "")
|
|
EXPECTED_REF = os.getenv("GITEA_EXPECTED_REF", "refs/heads/main")
|
|
EXPECTED_REPO = os.getenv("GITEA_REPO_FULL_NAME", "sagnik/Project_Velocity")
|
|
LOCK_FILE = os.getenv("DEPLOY_LOCK_FILE", "/tmp/desineuron-velocity-deploy.lock")
|
|
DEPLOY_CMD = os.getenv("DEPLOY_COMMAND", "/usr/local/bin/deploy_velocity_site.sh")
|
|
|
|
|
|
def verify_signature(secret: str, body: bytes, signature: str) -> bool:
|
|
if not secret:
|
|
return False
|
|
digest = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
|
|
return hmac.compare_digest(digest, signature or "")
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
server_version = "VelocityWebhook/1.0"
|
|
|
|
def _respond(self, code: int, payload: dict) -> None:
|
|
body = json.dumps(payload).encode("utf-8")
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, fmt: str, *args) -> None:
|
|
print("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), fmt % args))
|
|
|
|
def do_GET(self) -> None:
|
|
if self.path == "/health":
|
|
self._respond(200, {"ok": True, "service": "desineuron-velocity-gitea-webhook"})
|
|
return
|
|
self._respond(404, {"error": "not_found"})
|
|
|
|
def do_POST(self) -> None:
|
|
if self.path != PATH:
|
|
self._respond(404, {"error": "not_found"})
|
|
return
|
|
|
|
length = int(self.headers.get("Content-Length", "0"))
|
|
body = self.rfile.read(length)
|
|
signature = self.headers.get("X-Gitea-Signature", "")
|
|
event = self.headers.get("X-Gitea-Event", "")
|
|
|
|
if not verify_signature(SECRET, body, signature):
|
|
self._respond(401, {"error": "invalid_signature"})
|
|
return
|
|
|
|
try:
|
|
payload = json.loads(body.decode("utf-8"))
|
|
except json.JSONDecodeError:
|
|
self._respond(400, {"error": "invalid_json"})
|
|
return
|
|
|
|
if event == "ping":
|
|
self._respond(200, {"ok": True, "message": "ping_received"})
|
|
return
|
|
|
|
if event != "push":
|
|
self._respond(202, {"ok": True, "ignored": True, "reason": f"unsupported_event:{event}"})
|
|
return
|
|
|
|
ref = payload.get("ref")
|
|
repo_full_name = ((payload.get("repository") or {}).get("full_name")) or ""
|
|
|
|
if ref != EXPECTED_REF:
|
|
self._respond(202, {"ok": True, "ignored": True, "reason": f"unexpected_ref:{ref}"})
|
|
return
|
|
|
|
if EXPECTED_REPO and repo_full_name != EXPECTED_REPO:
|
|
self._respond(202, {"ok": True, "ignored": True, "reason": f"unexpected_repo:{repo_full_name}"})
|
|
return
|
|
|
|
cmd = ["/usr/bin/flock", "-n", LOCK_FILE, "/bin/bash", "-lc", DEPLOY_CMD]
|
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
|
|
|
if proc.returncode == 0:
|
|
self._respond(
|
|
202,
|
|
{
|
|
"ok": True,
|
|
"deployed": True,
|
|
"ref": ref,
|
|
"repository": repo_full_name,
|
|
"stdout_tail": proc.stdout.strip().splitlines()[-10:],
|
|
},
|
|
)
|
|
return
|
|
|
|
self._respond(
|
|
500,
|
|
{
|
|
"ok": False,
|
|
"deployed": False,
|
|
"ref": ref,
|
|
"repository": repo_full_name,
|
|
"returncode": proc.returncode,
|
|
"stdout_tail": proc.stdout.strip().splitlines()[-10:],
|
|
"stderr_tail": proc.stderr.strip().splitlines()[-10:],
|
|
},
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
server = ThreadingHTTPServer((HOST, PORT), Handler)
|
|
print(f"Velocity Gitea webhook listening on http://{HOST}:{PORT}{PATH}")
|
|
server.serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|