From 6a037d557c2779c4c1cebcc920032d04ee4f032b Mon Sep 17 00:00:00 2001 From: luna Date: Sun, 23 Aug 2026 02:31:32 +0000 Subject: [PATCH] relay: forward raw Gitea events unchanged --- README.md | 13 ++++--- services/dev/gitea-hermes-webhook-relay.nix | 9 ++--- services/dev/gitea-hermes-webhook-relay.py | 41 +++++++-------------- services/dev/gitea.nix | 2 +- 4 files changed, 26 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index ff34f2b..8c523a3 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,14 @@ scripts/ # deploy, edit_secrets Hosts compose by importing `common.nix` + whichever `services/*` modules they run. Each service module opens its own firewall ports. -## Gitea PR comment relay +## Gitea event relay -Mars includes a small HMAC-validating relay for Gitea webhooks. It normalizes -Gitea headers and forwards every authenticated JSON event to Hermes over -localhost; Hermes owns event selection, repository policy, and response -behavior. Jupiter's Gitea provisioning service registers the webhook -idempotently at `http://mars.orbit.sol:8645/gitea`. +Mars includes a small HMAC-validating relay for Gitea webhooks. It forwards the +authenticated request body unchanged, along with Gitea event and delivery +headers, to Hermes over localhost. The relay has no event, repository, action, +payload, or prompt policy; Hermes owns interpretation and response behavior. +Jupiter's Gitea provisioning service registers the webhook idempotently at +`http://mars.orbit.sol:8645/gitea`. Before deploying either host, add the same random `gitea_hermes_webhook_secret` value to both `secrets/mars.yaml` and diff --git a/services/dev/gitea-hermes-webhook-relay.nix b/services/dev/gitea-hermes-webhook-relay.nix index d6c089e..5e17830 100644 --- a/services/dev/gitea-hermes-webhook-relay.nix +++ b/services/dev/gitea-hermes-webhook-relay.nix @@ -42,12 +42,11 @@ in }; }; - # The relay forwards into a named Hermes webhook subscription. Keep the - # subscription declaratively present without replacing the rest of Hermes's - # runtime-managed webhook state. The secret is already in the container's - # environment file, but never appears in this unit or the Nix store. + # The relay forwards into a generic Hermes webhook subscription. Keep the + # subscription declaratively present without putting event policy or prompt + # text in this transport unit. Hermes owns interpretation and response policy. systemd.services.hermes-agent-webhook-route = { - description = "Configure Hermes Gitea PR comment webhook route"; + description = "Configure Hermes Gitea event webhook route"; wantedBy = [ "multi-user.target" ]; after = [ "podman-hermes-agent.service" ]; requires = [ "podman-hermes-agent.service" ]; diff --git a/services/dev/gitea-hermes-webhook-relay.py b/services/dev/gitea-hermes-webhook-relay.py index bec5249..c466b76 100644 --- a/services/dev/gitea-hermes-webhook-relay.py +++ b/services/dev/gitea-hermes-webhook-relay.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Normalize authenticated Gitea PR webhooks for Hermes Agent.""" +"""Relay authenticated Gitea webhook requests to Hermes Agent.""" from __future__ import annotations import hashlib @@ -40,13 +40,6 @@ def json_bytes(payload: dict) -> bytes: return json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode() -def normalized_event(headers, payload: dict) -> str: - event = headers.get("X-Gitea-Event-Type", "") or headers.get("X-Gitea-Event", "") - if event == "issue_comment" and payload.get("is_pull") is True: - return "pull_request_comment" - return event or "unknown" - - class Handler(BaseHTTPRequestHandler): server_version = "gitea-hermes-relay/1.0" @@ -82,15 +75,6 @@ class Handler(BaseHTTPRequestHandler): return body = self.rfile.read(content_length) - try: - payload = json.loads(body) - except json.JSONDecodeError: - self.send_json(400, {"status": "invalid_json"}) - return - if not isinstance(payload, dict): - self.send_json(400, {"status": "invalid_payload"}) - return - try: secret = load_secret() except RuntimeError as exc: @@ -107,22 +91,26 @@ class Handler(BaseHTTPRequestHandler): self.send_json(401, {"status": "invalid_signature"}) return - event = normalized_event(self.headers, payload) - normalized = dict(payload) - normalized["event_type"] = event - normalized["relay_source"] = "gitea" - forwarded_body = json_bytes(normalized) + # Keep the incoming body unchanged. Event interpretation and policy + # belong to Hermes, not to this transport service. + forwarded_body = body forwarded_signature = hmac.new( secret, forwarded_body, hashlib.sha256 ).hexdigest() + gitea_event = self.headers.get("X-Gitea-Event", "") + gitea_event_type = self.headers.get("X-Gitea-Event-Type", "") delivery_id = self.headers.get("X-Gitea-Delivery", "") forwarded_headers = { "Content-Type": "application/json", - "X-GitHub-Event": event, "X-Webhook-Signature": forwarded_signature, } + if gitea_event: + forwarded_headers["X-Gitea-Event"] = gitea_event + if gitea_event_type: + forwarded_headers["X-Gitea-Event-Type"] = gitea_event_type if delivery_id: forwarded_headers["X-Request-ID"] = delivery_id + forwarded_headers["X-Gitea-Delivery"] = delivery_id request = Request( HERMES_URL, @@ -148,12 +136,11 @@ class Handler(BaseHTTPRequestHandler): return LOG.info( - "forwarded %s action=%s delivery=%s", - event, - payload.get("action", ""), + "forwarded Gitea event=%s delivery=%s", + gitea_event or gitea_event_type or "unknown", delivery_id or "none", ) - self.send_json(200, {"status": "forwarded", "event": event}) + self.send_json(200, {"status": "forwarded"}) def main() -> None: diff --git a/services/dev/gitea.nix b/services/dev/gitea.nix index c473d15..3cf81a0 100644 --- a/services/dev/gitea.nix +++ b/services/dev/gitea.nix @@ -300,7 +300,7 @@ in # existing hook for the relay target or creates it when absent. Event policy # belongs to Hermes, so the source sends the complete Gitea event set. systemd.services.gitea-hermes-webhook-provision = { - description = "Provision Gitea webhook for Hermes PR comments"; + description = "Provision Gitea webhook for Hermes events"; after = [ "gitea.service" ]; requires = [ "gitea.service" ]; wantedBy = [ "multi-user.target" ];