relay: remove it; gitea already speaks Hermes's protocol
The relay existed on the premise that Gitea sends no header Hermes can read
an event name from, so something had to copy X-Gitea-Event into
X-GitHub-Event. That premise was wrong. Gitea's addDefaultHeaders sets
req.Header["X-GitHub-Delivery"] = []string{t.UUID}
req.Header["X-GitHub-Event"] = []string{event}
req.Header["X-GitHub-Event-Type"] = []string{eventType}
unconditionally, for every webhook type, alongside X-Hub-Signature-256 in
GitHub's exact format. (Direct map assignment rather than .Add() specifically
to keep the "GitHub" casing that canonicalisation would destroy.) Hermes
validates that signature on any route without provider gating and reads the
event name from that header, so gitea and hermes already speak the same
protocol and the translation layer was translating nothing.
Gitea now posts straight at http://mars.orbit.sol:8644/webhooks/gitea-pr-comments.
The URL path is the Hermes route name, so a second subscription is a second
hook and nothing else -- the route-in-path indirection the relay grew was a
reimplementation of something Hermes already had.
Removes the module, the 200-line relay, its test, the mars import, the 8645
listener, and the stale gitea-hermes-webhook-relay.service entry left in the
secret's restartUnits. hermes-agent-webhook-route moves to
hosts/mars/hermes-agent.nix, next to the container and the read-only prompt
and filter mounts it depends on.
Also makes that unit refuse to subscribe when GITEA_HERMES_WEBHOOK_SECRET is
unset in the container, matching the existing empty-prompt check. An empty
secret silently fails every delivery signature check afterwards while the
unit still reports success -- the worst possible failure shape, and one this
setup can actually produce on a first deploy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S94o42aQ8VkBmEWvDem5xa
This commit is contained in:
@@ -303,4 +303,87 @@ in
|
||||
requires = [ "hermes-agent-prepare-dirs.service" ];
|
||||
unitConfig.RequiresMountsFor = [ "/mnt/jupiter" ];
|
||||
};
|
||||
|
||||
# The Gitea PR-comment route. Gitea posts straight here (jupiter's
|
||||
# gitea-hermes-webhook-provision registers the hook at
|
||||
# http://mars.orbit.sol:8644/webhooks/gitea-pr-comments) -- there is no relay
|
||||
# in between. Gitea's addDefaultHeaders sends X-Hub-Signature-256 in GitHub's
|
||||
# exact format AND X-GitHub-Event, unconditionally, for every webhook type,
|
||||
# which is precisely what Hermes validates and reads the event name from.
|
||||
#
|
||||
# --events pull_request_comment narrows the route to the one event the prompt
|
||||
# handles; Gitea sends that value distinctly from issue_comment, so plain
|
||||
# issue comments never reach the agent. A route carries exactly one prompt,
|
||||
# so another event means either branching on {action} in the prompt or a
|
||||
# second subscription plus a second Gitea hook at /webhooks/<name>.
|
||||
#
|
||||
# No --deliver: it defaults to `log`. The prompt tells her to answer in the
|
||||
# pull request, so the PR comment IS the delivery.
|
||||
#
|
||||
# --script is the selection that MUST NOT be retunable at runtime.
|
||||
# gitea-pr-comment-filter.py drops luna's own comments before any LLM call,
|
||||
# which is what stops the reply loop: the prompt tells her to answer on the
|
||||
# PR, and her answer is itself a pull_request_comment. Both it and the prompt
|
||||
# are bind-mounted read-only from the store above so the agent cannot edit
|
||||
# its own guard out. Hermes resolves both names relative to ~/.hermes, hence
|
||||
# the bare filename.
|
||||
#
|
||||
# What read-only does NOT buy: it protects the sources, and this unit
|
||||
# re-subscribes from them on every start, so a restart restores the intended
|
||||
# prompt, filter and event list. The live subscription lives in
|
||||
# webhook_subscriptions.json under /opt/data and is hot-reloaded, which is
|
||||
# inside the agent's own write-safe root -- a self-modification sticks until
|
||||
# this unit next runs.
|
||||
#
|
||||
# The secret comes from the CONTAINER's environment, injected via
|
||||
# sops.templates."hermes-agent.env", which is why secrets.nix restarts
|
||||
# podman-hermes-agent BEFORE this unit on rotation: re-subscribing against a
|
||||
# container still holding the old value would silently pin the stale secret.
|
||||
systemd.services.hermes-agent-webhook-route = {
|
||||
description = "Configure Hermes Gitea PR-comment webhook route";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "podman-hermes-agent.service" ];
|
||||
requires = [ "podman-hermes-agent.service" ];
|
||||
path = [ pkgs.podman ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
# The container unit is ordered before us, but its gateway may still be
|
||||
# warming up while the image initializes its persistent state directory.
|
||||
for _ in $(seq 1 60); do
|
||||
if podman exec hermes-agent hermes webhook list >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Idempotency for the subscribe below, not cleanup: this removes only the
|
||||
# route this unit owns. Retiring an old route is a one-off done by hand,
|
||||
# so that a redeploy never silently deletes one added on purpose.
|
||||
podman exec hermes-agent hermes webhook remove gitea-pr-comments >/dev/null 2>&1 || true
|
||||
|
||||
# `set -eu` plus both emptiness checks are load-bearing. Without them a
|
||||
# missing prompt file or an unset secret yields an empty string, and the
|
||||
# subscription is created with an empty prompt or -- worse -- an empty
|
||||
# secret, which silently fails EVERY delivery signature check afterwards
|
||||
# while the unit still looks healthy. Fail loudly here instead.
|
||||
podman exec hermes-agent sh -c '
|
||||
set -eu
|
||||
[ -n "''${GITEA_HERMES_WEBHOOK_SECRET:-}" ] || {
|
||||
echo "GITEA_HERMES_WEBHOOK_SECRET is unset in the container" >&2; exit 1; }
|
||||
prompt="$(cat /opt/data/prompts/gitea-pr-comment.md)"
|
||||
[ -n "$prompt" ] || { echo "gitea-pr-comment prompt is empty" >&2; exit 1; }
|
||||
hermes webhook subscribe gitea-pr-comments \
|
||||
--secret "$GITEA_HERMES_WEBHOOK_SECRET" \
|
||||
--description "Gitea PR comments -> L.U.N.A." \
|
||||
--events pull_request_comment \
|
||||
--script gitea-pr-comment-filter.py \
|
||||
--prompt "$prompt"
|
||||
'
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user