The Production block never set one. inventory provision writes only
ansible_connection for a local host, so DOMAIN_PRIMARY stayed at the
group_vars default of infinito.localhost and the deployment served a zone its
own resolver knows nothing about. CoreDNS templates answers for
INFINITO_DOMAIN_RE and nothing else, so every lookup of the deployment's own
name came back NXDOMAIN.
Mastodon is where that surfaced. tootctl accounts create validates the address
through EmailMxValidator, which resolves MX and then, for the domain itself,
A and AAAA. All six queries returned NXDOMAIN in run 33574056480, the
validator recorded email :unreachable, and the admin account was never created.
Only the guide rows were affected: the matrix rows write
DOMAIN_PRIMARY: "{{ lookup('env', 'INFINITO_DOMAIN') }}" into their host_vars
and resolve fine.
The template now carries DOMAIN=<your-domain> in both the host and the
container form and threads it into --vars, with the container form passing it
through the docker run boundary as well. The two guide runners substitute it
the way they already substitute the SSH key, so the replay deploys under
INFINITO_DOMAIN like every other CI deploy. compose_deploy.sh moves its env
load ahead of the substitutions to have the value; host_deploy.sh had no env
load at all.
A production reader is asked for a domain now instead of silently inheriting
infinito.localhost, which no real deployment wants either. The shipped default
keeps its value: sys-svc-nss documents that *.localhost resolves through the
myhostname NSS module without any zone, and host-side tasks lean on it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.7 KiB
Hermes Agent
Description
Hermes Agent is an agent runtime from Nous Research that runs tool-using agent sessions against OpenAI-compatible model backends. In gateway mode it serves an OpenAI-compatible HTTP API, where clients address the agent with the same request shape they use for a plain model.
Overview
This role deploys Hermes Agent in gateway mode as a Docker service, published behind the reverse proxy on its own subdomain, with its compose service pinned to the isolating runtime that the Kata & gVisor role selects for the host. The API server is protected by a generated bearer key, and when the LiteLLM Gateway is part of the deployment the agent's model endpoint is pointed at that gateway with a per-consumer virtual key. Agent state lives in a named volume that the Backup Docker Volumes service captures when it is enabled.
Cosmos
The diagram places Hermes Agent in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
flowchart LR
subgraph deps [Dependencies]
dep_svc_ai_litellm["svc-ai-litellm 🐳🐝"]
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_svc_net_tor["svc-net-tor 🐳🐝"]
dep_svc_virt_kata["svc-virt-kata 💻"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
end
subgraph role [web-app-hermes 🐳🐝]
svc_hermes["hermes"]
svc_kata["kata"]
svc_sso["sso"]
svc_logout["logout ❌"]
svc_litellm["litellm"]
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_css["css"]
svc_prometheus["prometheus"]
svc_tor["tor"]
svc_container_backup["container_backup"]
end
subgraph dependents [Dependents]
dpt_svc_ai_robot["svc-ai-robot 💻"]
dpt_svc_db_qdrant["svc-db-qdrant 🐳🐝"]
dpt_web_app_baserow["web-app-baserow 🐳🐝"]
dpt_web_app_checkmk["web-app-checkmk 🐳🐝"]
dpt_web_app_fider["web-app-fider 🐳🐝"]
dpt_web_app_gitea["web-app-gitea 🐳🐝"]
dpt_web_app_gitlab["web-app-gitlab 🐳🐝"]
dpt_web_app_homeassistant["web-app-homeassistant 🐳🐝"]
dpt_web_app_jellyfin["web-app-jellyfin 🐳🐝"]
dpt_web_app_jenkins["web-app-jenkins 🐳🐝"]
dpt_web_app_listmonk["web-app-listmonk 🐳🐝"]
dpt_web_app_mattermost["web-app-mattermost 🐳🐝"]
dpt_more["..."]
end
dep_svc_ai_litellm -. "0..1" .-> svc_litellm
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_net_tor -. "0..1" .-> svc_tor
dep_svc_virt_kata -- "1:1" --> svc_kata
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_css -. "0..1" .-> svc_css
svc_hermes -- "1:1" --> dpt_more
svc_hermes -. "0..1" .-> dpt_svc_ai_robot
svc_hermes -. "0..1" .-> dpt_svc_db_qdrant
svc_hermes -. "0..1" .-> dpt_web_app_baserow
svc_hermes -. "0..1" .-> dpt_web_app_checkmk
svc_hermes -. "0..1" .-> dpt_web_app_fider
svc_hermes -. "0..1" .-> dpt_web_app_gitea
svc_hermes -. "0..1" .-> dpt_web_app_gitlab
svc_hermes -. "0..1" .-> dpt_web_app_homeassistant
svc_hermes -. "0..1" .-> dpt_web_app_jellyfin
svc_hermes -. "0..1" .-> dpt_web_app_jenkins
svc_hermes -. "0..1" .-> dpt_web_app_listmonk
svc_hermes -. "0..1" .-> dpt_web_app_mattermost
Solid 1:1 edges are fixed relationships; dashed 0..1 edges are conditional (enabled only in matching deployments); red 0..0 edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in meta/main.yml.
Features
- OpenAI-compatible gateway: The container starts the agent with the
gatewaycommand and answers OpenAI-style/v1requests on its own subdomain. - Bearer-key API server: The API server is gated by a generated
API_SERVER_KEY, and calls to/v1/modelswithout that key are rejected. - Isolating runtime: The compose service is pinned to Kata Containers when hardware virtualization and the Kata shim are present, and to gVisor otherwise.
- Gateway-routed models: With the LiteLLM Gateway deployed, the agent's model base URL points at that gateway and authenticates with a per-consumer virtual key.
- MCP client contract: With Home Assistant deployed, the role declares the MCP client side of the platform contract as an internal streamable-HTTP client with a read-only tool policy. Every discovered server is rendered into
config.yamlundermcp_serversin the agent'sHERMES_HOME, with the bearer referenced as${env:<ROLE>_MCP_TOKEN}so the config file carries no secret. The deploy then runshermes mcp testper server and fails when one does not answer. - Persistent agent data: Agent state is stored in a named volume mounted at
/opt/data, which the Backup Docker Volumes service captures without stopping the container. - Health endpoint: A
/healthendpoint reports the runtime status and backs the container healthcheck.
Quick Setup
Development
Clone, set up the workstation, and deploy Hermes Agent onto the local stack:
git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-hermes full_cycle=false
Production
Run the published image to provision the inventory and deploy Hermes Agent to a managed server (the mounted volume persists the inventory):
APP=web-app-hermes
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"
docker run --rm -it \
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
-e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
ghcr.io/infinito-nexus/core/debian bash -c '
INVENTORY=/etc/infinito.nexus/inventories/production
infinito administration inventory provision "$INVENTORY" \
--inventory-file "$INVENTORY/devices.yml" \
--host "$HOST" \
--include "$APP" \
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
--password-file "$INVENTORY/.password" \
--diff -vv'
MCP Client
Hermes consumes MCP; it does not serve one in this deployment. Every MCP server
role that is deployed alongside and marked shared is discovered through
MCP_DISCOVERED_SERVERS and rendered into the agent config at
/opt/data/config.yaml.
Configuration
| Property | Value |
|---|---|
| Direction | client |
| Transport | Streamable HTTP |
| Config file | /opt/data/config.yaml, owned by uid 10000 |
| Auth | Authorization header per server, from the discovery data |
The config file is rendered with the container user as owner. Rendering it as root makes the agent fall back to its built-in defaults silently, reporting only that the server is unknown.
Servers whose auth scheme no client renderer can present are filtered out before rendering, so the config never carries an unusable credential.
Verification
Hermes exposes no administrator-visible list of configured MCP servers, so the
configured set is proven at deploy time instead: the deploy runs hermes mcp test <server> for every configured server and fails when one does not answer. A
successful run reports the negotiated connection and the discovered tool count.
The Playwright spec covers the complementary case, that the agent API holding
the MCP credentials refuses an unauthenticated caller.
Default state
Off. mcp.enabled is false unless an MCP server role is part of the
deployment.
How to disable
Remove the MCP server roles, or pin mcp.enabled: false for this role.
The config then renders with an empty mcp_servers map and the verification step
is skipped.
Credits
Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).