mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-31 06:51:29 +00:00
BREAKING CHANGE: the container ENTRYPOINT is now /init (s6-overlay)
instead of /usr/bin/tini. Main hermes runs as the container CMD with
TTY inherited (preserving --tui), dashboard runs as a supervised s6-rc
service (HERMES_DASHBOARD=1 starts it; crashes auto-restart), and the
ground is laid for per-profile gateway supervision (Phase 3+4).
All five pre-s6 docker run invocation patterns continue to work
identically — verified by the Phase 0 docker harness:
docker run <image> → `hermes` with no args
docker run <image> chat -q "..." → `hermes chat -q ...` passthrough
docker run <image> sleep infinity → `sleep infinity` direct
docker run <image> bash → interactive bash
docker run -it <image> --tui → interactive Ink TUI
Phase 2 harness result: 12 passed, 2 xfailed (Phase 4 target). Hadolint
+ shellcheck pass cleanly.
Architecture pivot from plan v3 (documented in main-hermes/run header):
the plan called for main hermes to be an s6-supervised service, but
two real s6-overlay v3 mechanics blocked that — cont-init.d scripts
receive no arguments (CMD args are not visible to stage2-hook), and
`/run/s6/basedir/bin/halt` after writing the exit code did not
propagate the desired exit code (container exits 143). We use the
s6-overlay-native CMD pattern instead: main-wrapper.sh is the
container's main program (ENTRYPOINT prepends it so leading-dash
args like --version aren't intercepted by /init), exec's the final
program with stdin/stdout/stderr inherited, and the program's exit
code becomes the container exit code. main-hermes is now a no-op
`sleep infinity` slot kept for future supervised-gateway-container
modes. This trades "supervised restart of main hermes" for arg-
parity with the pre-s6 contract — main hermes was already unsupervised
under tini, so we lose nothing functional. Dashboard supervision is
the only new guarantee added by this phase.
Files added:
docker/main-wrapper.sh # arg routing + s6-setuidgid drop
docker/stage2-hook.sh # gosu-equivalent + chown + seed
docker/s6-rc.d/main-hermes/{type,run,dependencies.d/base}
docker/s6-rc.d/dashboard/{type,run,dependencies.d/base}
docker/s6-rc.d/user/contents.d/{main-hermes,dashboard}
Files changed:
Dockerfile: tini → s6-overlay install + ENTRYPOINT flip + service wiring
docker/entrypoint.sh: thin shim to stage2-hook.sh for back-compat
tests/docker/test_dashboard.py: add test_dashboard_restarts_after_crash
Refs: docs/plans/2026-05-07-s6-overlay-dynamic-subagent-gateways.md
30 lines
959 B
Text
Executable file
30 lines
959 B
Text
Executable file
#!/command/with-contenv sh
|
|
# shellcheck shell=sh
|
|
# Dashboard service. Always declared so s6 has a supervised slot; if
|
|
# HERMES_DASHBOARD isn't set to a truthy value we sleep forever and do
|
|
# nothing. See OQ3-A in the plan.
|
|
|
|
case "${HERMES_DASHBOARD:-}" in
|
|
1|true|TRUE|True|yes|YES|Yes) ;;
|
|
*) exec sleep infinity ;;
|
|
esac
|
|
|
|
cd /opt/data
|
|
# shellcheck disable=SC1091
|
|
. /opt/hermes/.venv/bin/activate
|
|
|
|
dash_host="${HERMES_DASHBOARD_HOST:-0.0.0.0}"
|
|
dash_port="${HERMES_DASHBOARD_PORT:-9119}"
|
|
|
|
# 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.
|
|
insecure=""
|
|
case "$dash_host" in
|
|
127.0.0.1|localhost) ;;
|
|
*) insecure="--insecure" ;;
|
|
esac
|
|
|
|
# shellcheck disable=SC2086 # word-splitting of $insecure is intentional
|
|
exec s6-setuidgid hermes hermes dashboard \
|
|
--host "$dash_host" --port "$dash_port" --no-open $insecure
|