hermes: CDP browser daemon + MCP client wiring for the agent
Adds a digest-pinned headless-shell Chrome DevTools Protocol daemon as a boot-persistent quadlet unit (chrome-devtools-protocol.container). The entrypoint override drops the image's socat front so the CDP endpoint binds loopback-only on 127.0.0.1:9222 in the host network namespace; only the two host-network consumers (Signal gateway, webui server) can drive it. hermes-webui flips Network=dankclaw_default -> Network=host so its agent process shares the daemon's namespace (0.0.0.0:8787 bind is LAN-facing as before; no exposure change). deploy_hermes installs the new unit and starts it if inactive without restarting in-flight browser sessions.
This commit is contained in:
1 parent
f1dd586fbb
commit
207235420c
4 files changed
+74
-10
No files matched your search
@@ -0,0 +1,38 @@
|
|||||||
|
# Quadlet unit: Chrome DevTools Protocol daemon for the hermes agent (rootless).
|
||||||
|
#
|
||||||
|
# Install path on dankclaw: ~/.config/containers/systemd/chrome-devtools-protocol.container
|
||||||
|
# Unit name: chrome-devtools-protocol.service (container name: chrome-devtools-protocol)
|
||||||
|
#
|
||||||
|
# Headless Chrome exposing raw CDP over http://127.0.0.1:9222. The hermes
|
||||||
|
# agent's chrome-devtools MCP client (mcp_servers.chrome-devtools in
|
||||||
|
# /data/config.yaml) spawns npx chrome-devtools-mcp with --browserUrl pointing
|
||||||
|
# at this endpoint and drives the browser over it. Both consumers — the Signal
|
||||||
|
# gateway and the webui server — run in the host network namespace, so a
|
||||||
|
# loopback-only bind is reachable by exactly the processes that may drive the
|
||||||
|
# browser and by nobody else on the LAN or WAN.
|
||||||
|
#
|
||||||
|
# Entrypoint override drops the image's socat front (which would bind
|
||||||
|
# 0.0.0.0:9222 in the host namespace and hand browser control to the whole
|
||||||
|
# LAN). --no-sandbox is required because rootless podman grants Chrome no user
|
||||||
|
# namespace for its sandbox; the content processes therefore run as the uid
|
||||||
|
# that already owns this container, capping the blast radius of a hostile page.
|
||||||
|
# Image pinned by digest: upgrades are a deliberate unit edit + redeploy, not a
|
||||||
|
# tag drift at pull time.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Chrome DevTools Protocol daemon (headless-shell) for hermes
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
ContainerName=chrome-devtools-protocol
|
||||||
|
Network=host
|
||||||
|
Image=docker.io/chromedp/headless-shell@sha256:2d349b544a1ea6b5b5fd7c0fe99215ff662339c57407ee2e8c0a11af93516b04
|
||||||
|
Entrypoint=/headless-shell/headless-shell
|
||||||
|
Exec=--no-sandbox --disable-gpu --use-gl=angle --use-angle=swiftshader --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222
|
||||||
|
ShmSize=1073741824
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Quadlet unit: Hermex webui (rootless, boot-persistent).
|
# Quadlet unit: Hermex webui (Hermex) container (rootless, boot-persistent).
|
||||||
#
|
#
|
||||||
# Install path on dankclaw: ~/.config/containers/systemd/hermes-webui.container
|
# Install path on dankclaw: ~/.config/containers/systemd/hermes-webui.container
|
||||||
# Unit name: hermes-webui.service (container name: hermes-webui)
|
# Unit name: hermes-webui.service (container name: hermes-webui)
|
||||||
@@ -7,10 +7,16 @@
|
|||||||
# foreground mode: bootstrap.py creates .venv inside the mounted checkout,
|
# foreground mode: bootstrap.py creates .venv inside the mounted checkout,
|
||||||
# installs WebUI + agent deps, then execs server.py as a single foreground
|
# installs WebUI + agent deps, then execs server.py as a single foreground
|
||||||
# process. It reads HERMES_HOME=/data, so it drives the SAME agent state
|
# process. It reads HERMES_HOME=/data, so it drives the SAME agent state
|
||||||
# (memory, skills, sessions) as the gateway container. Published on the LAN
|
# (memory, skills, sessions) as the gateway container.
|
||||||
# interface only (192.168.0.143:8787): password-gated (HERMES_WEBUI_PASSWORD
|
#
|
||||||
# in the host .env, git-ignored), so LAN clients connect directly and the
|
# Host networking on purpose: the agent's chrome-devtools MCP client spawns
|
||||||
# SSH tunnel (phone -> Dankclaw:22) remains the sole off-network ingress.
|
# npx chrome-devtools-mcp with --browserUrl=http://127.0.0.1:9222, and the CDP
|
||||||
|
# daemon binds loopback-only in the host network namespace (see
|
||||||
|
# chrome-devtools-protocol.container). Only processes sharing that namespace
|
||||||
|
# can drive the browser; a bridge-network client could not reach it at all.
|
||||||
|
# The server therefore listens on 0.0.0.0:8787 of the host netns (LAN-facing as
|
||||||
|
# before); access control is the password gate, and the SSH tunnel
|
||||||
|
# (phone -> Dankclaw:22) remains the sole off-network ingress path.
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Hermes webui (Hermex) container
|
Description=Hermes webui (Hermex) container
|
||||||
@@ -19,9 +25,8 @@ Description=Hermes webui (Hermex) container
|
|||||||
ContainerName=hermes-webui
|
ContainerName=hermes-webui
|
||||||
Image=localhost/hermes-agent:latest
|
Image=localhost/hermes-agent:latest
|
||||||
Pull=never
|
Pull=never
|
||||||
Network=dankclaw_default
|
Network=host
|
||||||
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env
|
||||||
Environment=HERMES_HOME=/data
|
|
||||||
Environment=HERMES_WEBUI_HOST=0.0.0.0
|
Environment=HERMES_WEBUI_HOST=0.0.0.0
|
||||||
Environment=HERMES_WEBUI_PORT=8787
|
Environment=HERMES_WEBUI_PORT=8787
|
||||||
Environment=HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD}
|
Environment=HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD}
|
||||||
@@ -30,7 +35,6 @@ Environment=HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD}
|
|||||||
Environment=HERMES_WEBUI_SKIP_ONBOARDING=1
|
Environment=HERMES_WEBUI_SKIP_ONBOARDING=1
|
||||||
Volume=/home/dank/hermes-data:/data
|
Volume=/home/dank/hermes-data:/data
|
||||||
Volume=/home/dank/hermes-webui:/webui
|
Volume=/home/dank/hermes-webui:/webui
|
||||||
PublishPort=192.168.0.143:8787:8787
|
|
||||||
Exec=python3 /webui/bootstrap.py --foreground
|
Exec=python3 /webui/bootstrap.py --foreground
|
||||||
# JSON exec form on purpose: this quadlet generator joins a bare HealthCmd
|
# JSON exec form on purpose: this quadlet generator joins a bare HealthCmd
|
||||||
# into ONE argv element with \x20 escapes and then shell-executes it — the
|
# into ONE argv element with \x20 escapes and then shell-executes it — the
|
||||||
|
|||||||
@@ -576,6 +576,15 @@ deploy_hermes() {
|
|||||||
# cannot be enabled by this host's v1 quadlet generator).
|
# cannot be enabled by this host's v1 quadlet generator).
|
||||||
local unit
|
local unit
|
||||||
mkdir -p "$quadlet_unit_dir"
|
mkdir -p "$quadlet_unit_dir"
|
||||||
|
# The CDP browser daemon is a companion service to the trio: install its
|
||||||
|
# unit and start it if inactive, but never restart it on deploy — a
|
||||||
|
# restart would kill in-flight browser sessions. Consumers (gateway,
|
||||||
|
# webui) reconnect with backoff.
|
||||||
|
cp "dankclaw/hermes/quadlet/chrome-devtools-protocol.container" "$quadlet_unit_dir/"
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
if ! systemctl --user is-active --quiet chrome-devtools-protocol.service; then
|
||||||
|
systemctl --user start chrome-devtools-protocol.service
|
||||||
|
fi
|
||||||
# The units run the image under a localhost/-aliased tag: this host's
|
# The units run the image under a localhost/-aliased tag: this host's
|
||||||
# quadlet path can fail to resolve registry-prefixed refs from local
|
# quadlet path can fail to resolve registry-prefixed refs from local
|
||||||
# storage at create time. Tag first, then (re)create via deploy.
|
# storage at create time. Tag first, then (re)create via deploy.
|
||||||
@@ -583,7 +592,6 @@ deploy_hermes() {
|
|||||||
for unit in hermes hermes-dashboard hermes-webui; do
|
for unit in hermes hermes-dashboard hermes-webui; do
|
||||||
cp "dankclaw/hermes/quadlet/$unit.container" "$quadlet_unit_dir/"
|
cp "dankclaw/hermes/quadlet/$unit.container" "$quadlet_unit_dir/"
|
||||||
done
|
done
|
||||||
systemctl --user daemon-reload
|
|
||||||
if ! systemctl --user is-active --quiet hermes.service; then
|
if ! systemctl --user is-active --quiet hermes.service; then
|
||||||
podman stop hermes hermes-dashboard hermes-webui 2>/dev/null || true
|
podman stop hermes hermes-dashboard hermes-webui 2>/dev/null || true
|
||||||
podman rm hermes hermes-dashboard hermes-webui 2>/dev/null || true
|
podman rm hermes hermes-dashboard hermes-webui 2>/dev/null || true
|
||||||
|
|||||||
@@ -43,10 +43,24 @@ both containers. The tracked, sanitized configuration baseline is
|
|||||||
- Model choice drives quality; the local `frank-default-large` (Dankclaw, 7770) is the intended backend.
|
- Model choice drives quality; the local `frank-default-large` (Dankclaw, 7770) is the intended backend.
|
||||||
- Do not expose the gateway without auth — put it behind [[Caddy]] with basic auth.
|
- Do not expose the gateway without auth — put it behind [[Caddy]] with basic auth.
|
||||||
|
|
||||||
|
## Browser automation (Chrome DevTools Protocol daemon)
|
||||||
|
|
||||||
|
The agent's browser tool is an MCP client (`mcp_tool.py` in the image) that
|
||||||
|
spawns `npx -y chrome-devtools-mcp@latest --browserUrl=http://127.0.0.1:9222`
|
||||||
|
per session; the browser itself lives in a dedicated daemon container, the
|
||||||
|
digest-pinned headless-shell unit `chrome-devtools-protocol.container` (host
|
||||||
|
network, loopback-only bind on 127.0.0.1:9222). Both consumers — the Signal
|
||||||
|
gateway and the webui server — run in the host network namespace, so they can
|
||||||
|
dial the daemon on loopback; nothing on the LAN can reach the CDP endpoint.
|
||||||
|
The wiring lives in the live `/data/config.yaml` under `mcp_servers.chrome-devtools`
|
||||||
|
(command `npx`, args `-y chrome-devtools-mcp@latest --browserUrl=http://127.0.0.1:9222`).
|
||||||
|
The npx package cache lives in the container layer, so a recreation (not just a
|
||||||
|
restart) pays one first-call npm download; subsequent calls are cached.
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [[Hermes-Dashboard]] — the LAN browser management UI
|
- [[Hermes-Dashboard]] — the LAN browser management UI
|
||||||
- [[Hermes-WebUI]] — the Hermex server (phone-facing API, SSH-tunneled)
|
- [[Hermes-WebUI]] — the Hermex server (phone-facing API, SSH-tunneled)
|
||||||
- [[Dankclaw Stack]]
|
- [[Dankclaw Stack]]
|
||||||
- [[OpenWebUI]] — the chat UI that pairs with Hermes
|
- [[OpenWebUI]] — the chat UI that pairs with Hermes
|
||||||
- Knowledge base: [[SearXNG]] (Hermes as the search agent)
|
- Knowledge base: [[SearXNG]] (Hermes as the search agent)
|
||||||
Reference in new issue
Block a user