#!/usr/bin/env python3 """Gitea push-webhook listener for soong-lab: on a verified push to main, run ~/soong-lab-deploy.sh. HMAC-SHA256 (X-Gitea-Signature) vs ~/.config/soong/webhook-secret. Logs each delivery (source IP / event / HMAC result / ref / action) to journald. Added 2026-07-14 to diagnose the gitea->:9010 REAL-delivery path after the ufw fix alone didn't restore auto-deploy (the prior `log_message = pass` made the listener silent, so we couldn't see whether gitea was delivering / authenticating / ref-matching).""" import hashlib, hmac, json, os, subprocess, sys, threading from http.server import BaseHTTPRequestHandler, HTTPServer SECRET = open(os.path.expanduser("~/.config/soong/webhook-secret"), "rb").read().strip() DEPLOY = os.path.expanduser("~/soong-lab-deploy.sh") STATUS = os.path.expanduser("~/.config/soong/last-deploy.json") def _log(msg): print(f"[webhook] {msg}", file=sys.stderr, flush=True) class H(BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", 0))) recv = self.headers.get("X-Gitea-Signature", "") mac = hmac.new(SECRET, body, hashlib.sha256).hexdigest() ok = hmac.compare_digest(mac, recv) event = self.headers.get("X-Gitea-Event", "?") _log(f"POST from {self.client_address[0]} event={event} clen={len(body)} " f"sig_recv={recv[:12]!r} sig_exp={mac[:12]!r} hmac_ok={ok}") if not ok: _log("-> 401 bad signature (gitea-hook secret != listener secret)") self.send_response(401); self.end_headers(); self.wfile.write(b"bad signature\n"); return try: ref = json.loads(body).get("ref", "") except Exception: _log("-> 400 bad json") self.send_response(400); self.end_headers(); return _log(f"ref={ref!r}") if ref != "refs/heads/main": _log("-> 200 ignored (ref != refs/heads/main)") self.send_response(200); self.end_headers(); self.wfile.write(b"ignored " + ref.encode() + b"\n"); return _log("-> 202 deploying") self.send_response(202); self.end_headers(); self.wfile.write(b"deploying\n") threading.Thread(target=lambda: subprocess.run(["bash", DEPLOY]), daemon=True).start() def do_GET(self): self.send_response(200); self.end_headers() try: st = open(STATUS).read().strip() except Exception: st = "no deploy yet" self.wfile.write(b"soong-webhook ok | last: " + st.encode()) def log_message(self, *a): pass HTTPServer(("0.0.0.0", 9010), H).serve_forever()