#!/usr/bin/env python3 """Gitea push-webhook listener: on a verified push to main, trigger ~/arbo-deploy.sh. HMAC-SHA256 validated against ~/.config/arbo/webhook-secret (X-Gitea-Signature).""" import hashlib, hmac, json, os, subprocess, threading from http.server import BaseHTTPRequestHandler, HTTPServer SECRET = open(os.path.expanduser("~/.config/arbo/webhook-secret"), "rb").read().strip() DEPLOY = os.path.expanduser("~/arbo-deploy.sh") class H(BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", 0))) mac = hmac.new(SECRET, body, hashlib.sha256).hexdigest() if not hmac.compare_digest(mac, self.headers.get("X-Gitea-Signature", "")): self.send_response(401); self.end_headers(); self.wfile.write(b"bad signature\n"); return try: ref = json.loads(body).get("ref", "") except Exception: self.send_response(400); self.end_headers(); return if ref != "refs/heads/main": self.send_response(200); self.end_headers(); self.wfile.write(b"ignored " + ref.encode() + b"\n"); return 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(); self.wfile.write(b"arbo-webhook ok\n") def log_message(self, *a): pass HTTPServer(("0.0.0.0", 9009), H).serve_forever()