Files
homelab/hosts/mars/secrets.nix
T
darmanandClaude Opus 5 2a1a1628e1 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
2026-08-23 08:09:01 +02:00

76 lines
3.8 KiB
Nix

{ config, ... }:
# sops-nix wiring for mars. Encrypted values in ../../secrets/mars.yaml,
# decrypted with mars's own SSH host key (recipient in ../../.sops.yaml).
# The host key is pre-generated on the laptop and shipped at install
# (nixos-anywhere --extra-files -> /etc/ssh/ssh_host_ed25519_key).
{
sops.defaultSopsFile = ../../secrets/mars.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.secrets.darman_password.neededForUsers = true;
users.users.darman.hashedPasswordFile = config.sops.secrets.darman_password.path;
sops.secrets.tailscale_authkey = { };
# Credentials file for the //jupiter/data cifs mount (see configuration.nix).
# Same value as jupiter's own samba_password (services/network/samba.nix) —
# mars authenticates as the same smb user, mirroring terra's setup.
sops.secrets.samba_password = { };
sops.templates."jupiter-smb.credentials".content = ''
username=darman
password=${config.sops.placeholder.samba_password}
'';
# Hermes Agent (hermes-agent.nix) — moved here from jupiter (see that
# host's git history); same Telegram bot token, opencode key, and
# Authentik OIDC client secret, so no new bot/app to provision.
sops.secrets.opencode_go_api_key = { };
sops.secrets.telegram_bot_token = { };
sops.secrets.hermes_dashboard_oidc_client_secret = { };
# Add the same value to secrets/mars.yaml before deploying Mars, and store
# it WITHOUT a trailing newline: it reaches Hermes through the env template
# below, where a newline would both corrupt the env file and change the key
# the HMAC is computed with. `scripts/edit_secrets` writes a bare value.
#
# podman-hermes-agent is in restartUnits for a reason that is easy to miss:
# the secret reaches the container only through sops.templates, whose
# rendered PATH never changes, so the container unit's definition is
# identical before and after the secret is added and systemd will NOT
# restart it on its own. Without this line the very first deploy leaves the
# container holding an empty GITEA_HERMES_WEBHOOK_SECRET, and
# hermes-agent-webhook-route (which reads it back out of the running
# container) subscribes with an empty secret — every delivery then fails
# signature validation inside Hermes with no obvious cause. That unit now
# refuses to subscribe on an unset secret rather than doing it quietly, but
# the ordering here is still what makes the rotation correct.
sops.secrets.gitea_hermes_webhook_secret = {
restartUnits = [
"podman-hermes-agent.service"
"hermes-agent-webhook-route.service"
];
};
sops.templates."hermes-agent.env".content = ''
OPENCODE_GO_API_KEY=${config.sops.placeholder.opencode_go_api_key}
TELEGRAM_BOT_TOKEN=${config.sops.placeholder.telegram_bot_token}
TELEGRAM_HOME_CHANNEL=15151223
TELEGRAM_ALLOWED_USERS=15151223
WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644
GITEA_HERMES_WEBHOOK_SECRET=${config.sops.placeholder.gitea_hermes_webhook_secret}
HERMES_DASHBOARD_OIDC_CLIENT_SECRET=${config.sops.placeholder.hermes_dashboard_oidc_client_secret}
'';
# luna's own gitea push token (services/dev/gitea.nix provisions the
# account + PR-tier repo access on jupiter; this is the per-user token
# generated once via `gitea admin user generate-access-token --username
# luna --scopes write:repository,read:user` on jupiter — read:user is
# required, `tea logins add` fails without it). Read directly by
# hermes-agent.nix's prepare-dirs oneshot (default root:root owner is
# fine — that oneshot already runs as root) to set up a git
# credential-store file and a `tea` login, both written into hermesHome
# so they're visible inside the container at /opt/data/....
# restartUnits re-provisions both on rotation, without a full mars deploy.
sops.secrets.gitea_luna_token.restartUnits = [ "hermes-agent-prepare-dirs.service" ];
}