Podman's port forwarder dials the container bridge IP; a container-internal loopback bind made every tunneled connection reset.
107 lines
3.7 KiB
Bash
107 lines
3.7 KiB
Bash
#!/usr/bin/env bash
|
|
# Seam: hermes-webui (the Hermex server) deployment integrity.
|
|
# The webui is the phone-facing API for the Hermes agent. It must stay
|
|
# loopback-bound so the SSH tunnel is its only ingress, keep its password in
|
|
# the host .env, share the gateway data volume, and deploy as an isolated
|
|
# compose service that cannot drag the gateway or inference along with it.
|
|
|
|
load helpers
|
|
|
|
COMPOSE="$REPO_ROOT/dankclaw/compose.yaml"
|
|
ENV_EXAMPLE="$REPO_ROOT/dankclaw/.env.example"
|
|
PLANNER="$REPO_ROOT/scripts/deploy-plan.py"
|
|
DEPLOY="$REPO_ROOT/scripts/deploy.sh"
|
|
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
|
|
|
|
webui_service_yaml() {
|
|
python3 - "$COMPOSE" <<'PYEOF'
|
|
import sys, yaml
|
|
with open(sys.argv[1]) as fh:
|
|
data = yaml.safe_load(fh)
|
|
svc = (data.get("services") or {}).get("hermes-webui")
|
|
assert svc, "dankclaw/compose.yaml: no hermes-webui service"
|
|
print(yaml.safe_dump(svc))
|
|
PYEOF
|
|
}
|
|
|
|
@test "hermes-webui runs the webui bootstrap in foreground mode" {
|
|
local y
|
|
y=$(webui_service_yaml)
|
|
grep -q 'nousresearch/hermes-agent' <<< "$y"
|
|
grep -q 'bootstrap.py' <<< "$y"
|
|
grep -q -- '--foreground' <<< "$y"
|
|
}
|
|
|
|
@test "hermes-webui is loopback-bound and password-gated" {
|
|
local y
|
|
y=$(webui_service_yaml)
|
|
# Host-side publish stays on loopback: the SSH tunnel is the only ingress.
|
|
grep -q '127.0.0.1:8787' <<< "$y"
|
|
grep -q 'HERMES_WEBUI_PASSWORD' <<< "$y"
|
|
# Container-internal bind must be 0.0.0.0 or Podman's port forwarder
|
|
# (which dials the bridge IP) resets every connection.
|
|
grep -q 'HERMES_WEBUI_HOST: 0.0.0.0' <<< "$y"
|
|
}
|
|
|
|
@test "hermes-webui shares the gateway data volume and mounts the webui checkout" {
|
|
local y
|
|
y=$(webui_service_yaml)
|
|
grep -q 'HERMES_HOME: /data' <<< "$y"
|
|
grep -q '/home/dank/hermes-data:/data' <<< "$y"
|
|
grep -q '/home/dank/hermes-webui:/webui' <<< "$y"
|
|
}
|
|
|
|
@test "hermes-webui has a healthcheck that probes /health" {
|
|
local y
|
|
y=$(webui_service_yaml)
|
|
grep -q 'healthcheck' <<< "$y"
|
|
grep -q '/health' <<< "$y"
|
|
}
|
|
|
|
@test "dankclaw .env.example defines the webui password placeholder" {
|
|
env_vars_defined "$ENV_EXAMPLE" | grep -qx 'HERMES_WEBUI_PASSWORD'
|
|
}
|
|
|
|
# Revision-aware planner: a change to only the hermes-webui service block must
|
|
# plan hermes-webui and nothing else (the gateway is unchanged).
|
|
webui_fixture_repo() {
|
|
local dir="$1"
|
|
git init -q "$dir"
|
|
git -C "$dir" config user.email t@t && git -C "$dir" config user.name t
|
|
mkdir -p "$dir/dankclaw"
|
|
cat > "$dir/dankclaw/compose.yaml" <<'EOF'
|
|
services:
|
|
hermes:
|
|
image: docker.io/nousresearch/hermes-agent:latest
|
|
hermes-webui:
|
|
image: docker.io/nousresearch/hermes-agent:latest
|
|
ports:
|
|
- "127.0.0.1:8787:8787"
|
|
EOF
|
|
git -C "$dir" add -A && git -C "$dir" commit -qm base
|
|
# Touch only the webui service block (its port line is unique in the file).
|
|
sed -i 's|127.0.0.1:8787:8787|127.0.0.1:8788:8787|' "$dir/dankclaw/compose.yaml"
|
|
git -C "$dir" commit -qam head
|
|
}
|
|
|
|
@test "planner narrows a hermes-webui compose edit to hermes-webui only" {
|
|
local dir base head
|
|
dir=$(mktemp -d)
|
|
webui_fixture_repo "$dir"
|
|
base=$(git -C "$dir" rev-parse HEAD~1)
|
|
head=$(git -C "$dir" rev-parse HEAD)
|
|
run python3 "$PLANNER" --target dankclaw --repo "$dir" \
|
|
--base "$base" --head "$head" --paths dankclaw/compose.yaml
|
|
[ "$status" -eq 0 ]
|
|
[ "$output" = "hermes-webui" ]
|
|
}
|
|
|
|
@test "remote deployer accepts and dispatches hermes-webui" {
|
|
grep -q 'dankclaw:hermes-webui' "$REMOTE_DEPLOY"
|
|
awk '/^deploy_compose_service\(\)/,/^}/' "$REMOTE_DEPLOY" | grep -q 'hermes-webui'
|
|
}
|
|
|
|
@test "top-level deployer accepts hermes-webui as a Dankclaw service" {
|
|
grep -q 'dankclaw:hermes-webui' "$DEPLOY"
|
|
}
|