diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 299aab97a2..65386e53dd 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -86,6 +86,41 @@ if [ -d "$INSTALL_DIR/skills" ]; then python3 "$INSTALL_DIR/tools/skills_sync.py" fi +# Optionally start `hermes dashboard` as a side-process. +# +# Toggled by HERMES_DASHBOARD=1 (also accepts "true"/"yes", case-insensitive). +# Host/port/TUI can be overridden via: +# HERMES_DASHBOARD_HOST (default 0.0.0.0 — exposed outside the container) +# HERMES_DASHBOARD_PORT (default 9119, matches `hermes dashboard` default) +# HERMES_DASHBOARD_TUI (already honored by `hermes dashboard` itself) +# +# The dashboard is a long-lived server. We background it *before* the final +# `exec hermes "$@"` so the user's chosen foreground command (chat, gateway, +# sleep infinity, …) remains PID-of-interest for the container runtime. When +# the container stops the whole process tree is torn down, so no explicit +# cleanup is needed. +case "${HERMES_DASHBOARD:-}" in + 1|true|TRUE|True|yes|YES|Yes) + dash_host="${HERMES_DASHBOARD_HOST:-0.0.0.0}" + dash_port="${HERMES_DASHBOARD_PORT:-9119}" + dash_args=(--host "$dash_host" --port "$dash_port" --no-open) + # Binding to anything other than localhost requires --insecure — the + # dashboard refuses otherwise because it exposes API keys. Inside a + # container this is the expected deployment (host reaches it via + # published port), so opt in automatically. + if [ "$dash_host" != "127.0.0.1" ] && [ "$dash_host" != "localhost" ]; then + dash_args+=(--insecure) + fi + echo "Starting hermes dashboard on ${dash_host}:${dash_port} (background)" + # Prefix dashboard output so it's distinguishable from the main + # process in `docker logs`. stdbuf keeps the pipe line-buffered. + ( + stdbuf -oL -eL hermes dashboard "${dash_args[@]}" 2>&1 \ + | sed -u 's/^/[dashboard] /' + ) & + ;; +esac + # Final exec: two supported invocation patterns. # # docker run -> exec `hermes` with no args (legacy default) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 014a938e07..97ebf9e29d 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -470,10 +470,23 @@ except (ValueError, TypeError): ) _GATEWAY_HEALTH_TIMEOUT = 3.0 +# DEPRECATED (scheduled for removal): GATEWAY_HEALTH_URL / GATEWAY_HEALTH_TIMEOUT. +# Cross-container / cross-host gateway liveness detection will be folded into a +# first-class dashboard config key so it's no longer Docker-adjacent lore buried +# in env vars. The env vars still work for now so existing Compose deployments +# don't break. Do not add new callers — wire new uses through the planned +# config surface. + def _probe_gateway_health() -> tuple[bool, dict | None]: """Probe the gateway via its HTTP health endpoint (cross-container). + .. deprecated:: + Driven by the deprecated ``GATEWAY_HEALTH_URL`` / + ``GATEWAY_HEALTH_TIMEOUT`` env vars. Scheduled for removal alongside + a move to a first-class dashboard config key. See + :data:`_GATEWAY_HEALTH_URL` for context. + Uses ``/health/detailed`` first (returns full state), falling back to the simpler ``/health`` endpoint. Returns ``(is_alive, body_dict)``. diff --git a/website/docs/user-guide/docker.md b/website/docs/user-guide/docker.md index 21f8246ace..2a13fe6662 100644 --- a/website/docs/user-guide/docker.md +++ b/website/docs/user-guide/docker.md @@ -45,28 +45,33 @@ Opening any port on an internet facing machine is a security risk. You should no ## Running the dashboard -The built-in web dashboard can run alongside the gateway as a separate container. - -To run the dashboard as its own container, point it at the gateway's health endpoint so it can detect gateway status across containers: +The built-in web dashboard runs as an optional side-process inside the same container as the gateway. Set `HERMES_DASHBOARD=1` and expose port `9119` alongside the gateway's `8642`: ```sh docker run -d \ - --name hermes-dashboard \ + --name hermes \ --restart unless-stopped \ -v ~/.hermes:/opt/data \ + -p 8642:8642 \ -p 9119:9119 \ - -e GATEWAY_HEALTH_URL=http://$HOST_IP:8642 \ - nousresearch/hermes-agent dashboard + -e HERMES_DASHBOARD=1 \ + nousresearch/hermes-agent gateway run ``` -Replace `$HOST_IP` with the IP address of the machine running the gateway container (e.g. `192.168.1.100`), or use a Docker network hostname if both containers share a network (see the [Compose example](#docker-compose-example) below). +The entrypoint starts `hermes dashboard` in the background (running as the non-root `hermes` user) before `exec`-ing the main command. Dashboard output is prefixed with `[dashboard]` in `docker logs` so it's easy to separate from gateway logs. | Environment variable | Description | Default | |---------------------|-------------|---------| -| `GATEWAY_HEALTH_URL` | Base URL of the gateway's API server, e.g. `http://gateway:8642` | *(unset — local PID check only)* | -| `GATEWAY_HEALTH_TIMEOUT` | Health probe timeout in seconds | `3` | +| `HERMES_DASHBOARD` | Set to `1` (or `true` / `yes`) to launch the dashboard alongside the main command | *(unset — dashboard not started)* | +| `HERMES_DASHBOARD_HOST` | Bind address for the dashboard HTTP server | `0.0.0.0` | +| `HERMES_DASHBOARD_PORT` | Port for the dashboard HTTP server | `9119` | +| `HERMES_DASHBOARD_TUI` | Set to `1` to expose the in-browser Chat tab (embedded `hermes --tui` via PTY/WebSocket) | *(unset)* | -Without `GATEWAY_HEALTH_URL`, the dashboard falls back to local process detection — which only works when the gateway runs in the same container or on the same host. +The default `HERMES_DASHBOARD_HOST=0.0.0.0` is required for the host to reach the dashboard through the published port; the entrypoint automatically passes `--insecure` to `hermes dashboard` in that case. Override to `127.0.0.1` if you want to restrict the dashboard to in-container access only (e.g. behind a reverse proxy in a sidecar). + +:::note +The dashboard side-process is **not supervised** — if it crashes, it stays down until the container restarts. Running it as a separate container is not supported: the dashboard's gateway-liveness detection requires a shared PID namespace with the gateway process. +::: ## Running interactively (CLI chat) @@ -102,7 +107,7 @@ The `/opt/data` volume is the single source of truth for all Hermes state. It ma | `skins/` | Custom CLI skins | :::warning -Never run two Hermes **gateway** containers against the same data directory simultaneously — session files and memory stores are not designed for concurrent write access. Running a dashboard container alongside the gateway is safe since the dashboard only reads data. +Never run two Hermes **gateway** containers against the same data directory simultaneously — session files and memory stores are not designed for concurrent write access. ::: ## Multi-profile support @@ -188,49 +193,24 @@ services: restart: unless-stopped command: gateway run ports: - - "8642:8642" + - "8642:8642" # gateway API + - "9119:9119" # dashboard (only reached when HERMES_DASHBOARD=1) volumes: - ~/.hermes:/opt/data - networks: - - hermes-net - # Uncomment to forward specific env vars instead of using .env file: - # environment: - # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - # - OPENAI_API_KEY=${OPENAI_API_KEY} - # - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + environment: + - HERMES_DASHBOARD=1 + # Uncomment to forward specific env vars instead of using .env file: + # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + # - OPENAI_API_KEY=${OPENAI_API_KEY} + # - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} deploy: resources: limits: memory: 4G cpus: "2.0" - - dashboard: - image: nousresearch/hermes-agent:latest - container_name: hermes-dashboard - restart: unless-stopped - command: dashboard --host 0.0.0.0 --insecure - ports: - - "9119:9119" - volumes: - - ~/.hermes:/opt/data - environment: - - GATEWAY_HEALTH_URL=http://hermes:8642 - networks: - - hermes-net - depends_on: - - hermes - deploy: - resources: - limits: - memory: 512M - cpus: "0.5" - -networks: - hermes-net: - driver: bridge ``` -Start with `docker compose up -d` and view logs with `docker compose logs -f`. +Start with `docker compose up -d` and view logs with `docker compose logs -f`. Dashboard output is prefixed with `[dashboard]` so it's easy to filter from gateway logs. ## Resource limits @@ -273,6 +253,7 @@ The entrypoint script (`docker/entrypoint.sh`) bootstraps the data volume on fir - Copies default `config.yaml` if missing - Copies default `SOUL.md` if missing - Syncs bundled skills using a manifest-based approach (preserves user edits) +- Optionally launches `hermes dashboard` as a background side-process when `HERMES_DASHBOARD=1` (see [Running the dashboard](#running-the-dashboard)) - Then runs `hermes` with whatever arguments you pass ## Upgrading