`hermes webhook subscribe` has no --toolsets flag, so a webhook run got
Hermes's constrained default (web_search, web_extract, vision_analyze,
clarify) -- no shell, no file access, which meant neither prompt could
actually be carried out: luna was woken, read the comment, and had no way
to act on it. Upstream's documented answer is to add the `toolsets` key to
webhook_subscriptions.json by hand, and a hand edit does not survive this
unit's re-provision. So the whole route definition moves here and the CLI
is not used at all.
The file is written host-side with jq. hermesHome is the bind-mount source
for /opt/data, so the container sees the same inode and hot-reloads it on
the next delivery -- no podman exec, no readiness loop, and no quoting
chain between nix and the prompt text. The merge is per-route: routes this
unit does not name survive, created_at is carried over, and every other
key is replaced outright so a hand-added `deliver_only` or `filters`
cannot linger.
The secret now comes from the sops file directly instead of being read
back out of the container's environment, which drops podman-hermes-agent
from restartUnits (the ordering constraint it existed for is gone) and
takes GITEA_HERMES_WEBHOOK_SECRET out of an env var luna can read.
The new gitea-pr-reviews route covers reviews with a body and
changes-requested. Those are not IssueCommentPayloads: gitea sends a
PullRequestPayload with action "reviewed" and a `review` object of exactly
{type, content} -- no review id, no line comments. So the prompt fetches
them with `tea pulls review-comments` and acts only on ones whose
`resolver` is empty, resolving each as it goes; with no stable id in the
payload, resolved state is the only workable duplicate-delivery guard.
An empty review body is deliberately NOT a drop, unlike in the comment
filter: a review whose substance is entirely in line comments has none.
Approvals are left unsubscribed -- an approval is darman signing off, not
asking for work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S94o42aQ8VkBmEWvDem5xa
69 lines
3.5 KiB
Nix
69 lines
3.5 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 = { };
|
|
# Same value as in secrets/jupiter.yaml (the sending side), stored WITHOUT a
|
|
# trailing newline — a stray newline would change the key the HMAC is
|
|
# computed with and fail every delivery. `scripts/edit_secrets` writes a
|
|
# bare value. hermes-agent.nix trims one anyway, belt and braces.
|
|
#
|
|
# This is NOT in the container's env any more. It used to be, because
|
|
# hermes-agent-webhook-route ran `hermes webhook subscribe` inside the
|
|
# container and read the secret back out of its environment — which meant
|
|
# podman-hermes-agent had to be restarted first on rotation, or the
|
|
# subscription silently pinned the stale value. The route config is now
|
|
# written host-side (hermes-agent-webhook-routes reads this file directly),
|
|
# so that ordering constraint is gone and the secret no longer sits in an
|
|
# env var luna can read with `env`.
|
|
sops.secrets.gitea_hermes_webhook_secret = {
|
|
restartUnits = [ "hermes-agent-webhook-routes.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
|
|
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" ];
|
|
}
|