#!/usr/bin/env python3 """Simple webhook handler for Gitea push events""" import http.server import subprocess import threading import json import hmac import hashlib import os WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'gururmm-build-secret') BUILD_SCRIPT = '/opt/gururmm/build-agents.sh' LOCK_FILE = '/var/run/gururmm-build.lock' PORT = 9000 def is_build_running(): if not os.path.exists(LOCK_FILE): return False with open(LOCK_FILE) as f: pid = f.read().strip() try: pid = int(pid) except ValueError: os.remove(LOCK_FILE) return False # Check if process exists and is not a zombie try: with open(f'/proc/{pid}/status') as f: status = f.read() if 'State:\tZ' in status: # Zombie — build is done, clean up try: os.waitpid(pid, os.WNOHANG) except ChildProcessError: pass os.remove(LOCK_FILE) return False return True except FileNotFoundError: os.remove(LOCK_FILE) return False def run_build(): proc = subprocess.Popen( ['sudo', BUILD_SCRIPT], stdout=open('/var/log/gururmm-build.log', 'a'), stderr=subprocess.STDOUT ) with open(LOCK_FILE, 'w') as f: f.write(str(proc.pid)) proc.wait() # Reap the child — prevents zombie if os.path.exists(LOCK_FILE): os.remove(LOCK_FILE) print(f"Build complete, exit code: {proc.returncode}") class WebhookHandler(http.server.BaseHTTPRequestHandler): def log_message(self, format, *args): pass # suppress default access log noise def do_POST(self): if self.path != '/webhook/build': self.send_response(404) self.end_headers() return content_length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(content_length) signature = self.headers.get('X-Gitea-Signature', '') if signature: expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest() if not hmac.compare_digest(signature, expected): self.send_response(403) self.end_headers() self.wfile.write(b'Invalid signature') return try: data = json.loads(body) ref = data.get('ref', '') if ref == 'refs/heads/main': if is_build_running(): self.send_response(200) self.end_headers() self.wfile.write(b'Build already in progress, skipped') return print(f"Triggering build for push to main") t = threading.Thread(target=run_build, daemon=True) t.start() self.send_response(200) self.end_headers() self.wfile.write(b'Build triggered') else: self.send_response(200) self.end_headers() self.wfile.write(f'Ignored push to {ref}'.encode()) except Exception as e: print(f"Error: {e}") self.send_response(500) self.end_headers() self.wfile.write(str(e).encode()) if __name__ == '__main__': server = http.server.HTTPServer(('127.0.0.1', PORT), WebhookHandler) print(f'Webhook handler listening on port {PORT}') server.serve_forever()