sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-15 15:23:02

Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-15 15:23:02
This commit is contained in:
2026-05-15 15:23:05 -07:00
parent 6d6de33cb7
commit 31088cb8de
21 changed files with 1367 additions and 1 deletions

View File

@@ -0,0 +1,109 @@
#!/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()