Author: Mike Swanson Machine: DESKTOP-0O8A1RL Timestamp: 2026-05-15 15:23:02
110 lines
3.4 KiB
Python
110 lines
3.4 KiB
Python
#!/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()
|