diff --git a/install/hiclaw-verify.sh b/install/hiclaw-verify.sh index e744f5a0e..bcd215f80 100755 --- a/install/hiclaw-verify.sh +++ b/install/hiclaw-verify.sh @@ -3,6 +3,7 @@ # # Usage: # bash install/hiclaw-verify.sh [container_name] # default: agentteams-manager +# AGENTTEAMS_VERIFY_INFRA_CONTAINER= # default: agentteams-controller # # Runs 6 read-only reachability checks and prints PASS/FAIL per check. # Exit code: 0 if all pass, 1 if any fail. @@ -22,8 +23,9 @@ # kubectl get pod -l app=agentteams-manager -o jsonpath='{.items[0].metadata.name}' # # 2. Internal service checks (checks #2, #3, #6) -# These use `docker exec ... curl 127.0.0.1:PORT` which works because all -# services share a single container network namespace. +# Local embedded installs route infrastructure checks to the controller +# container and the Agent check to the Manager container. Legacy installs +# fall back to the Manager container for all three checks. # In K8s each service is a separate Pod/Service; replace with: # `kubectl exec -- curl http://..svc:PORT` # or use `kubectl port-forward svc/ LOCAL:REMOTE` for a one-shot probe. @@ -39,7 +41,8 @@ # No set -e: each check is independent; failures do not abort subsequent checks. -CONTAINER="${1:-agentteams-manager}" +MANAGER_CONTAINER="${1:-agentteams-manager}" +INFRA_CONTAINER="${AGENTTEAMS_VERIFY_INFRA_CONTAINER:-agentteams-controller}" # ---------- Docker/Podman detection ---------- # TODO(k8s): extend to three-way detection (docker / podman / kubectl) @@ -52,13 +55,36 @@ if ! docker version >/dev/null 2>&1; then fi fi -# ---------- Port/config detection from container env ---------- +# ---------- Local topology and port/config detection ---------- # TODO(k8s): replace printenv-based detection with kubectl-based service # discovery, or accept GATEWAY_URL / CONSOLE_URL env vars directly. -container_env=$("${DOCKER_CMD}" exec "${CONTAINER}" printenv 2>/dev/null) || container_env="" -PORT_GATEWAY=$(echo "$container_env" | grep ^AGENTTEAMS_PORT_GATEWAY= | cut -d= -f2-) -PORT_CONSOLE=$(echo "$container_env" | grep ^AGENTTEAMS_PORT_CONSOLE= | cut -d= -f2-) +running_containers=$("${DOCKER_CMD}" ps --format '{{.Names}}' 2>/dev/null) || running_containers="" +SERVICE_CONTAINER="${MANAGER_CONTAINER}" +if printf '%s\n' "${running_containers}" | grep -Fxq "${INFRA_CONTAINER}"; then + SERVICE_CONTAINER="${INFRA_CONTAINER}" +fi + +manager_env=$("${DOCKER_CMD}" exec "${MANAGER_CONTAINER}" printenv 2>/dev/null) || manager_env="" +if [ "${SERVICE_CONTAINER}" = "${MANAGER_CONTAINER}" ]; then + service_env="${manager_env}" +else + service_env=$("${DOCKER_CMD}" exec "${SERVICE_CONTAINER}" printenv 2>/dev/null) || service_env="" +fi + +mapped_host_port() { + local container="$1" + local container_port="$2" + local mapping + + mapping=$("${DOCKER_CMD}" port "${container}" "${container_port}/tcp" 2>/dev/null | head -1) || mapping="" + printf '%s\n' "${mapping}" | awk -F: 'NF > 1 { print $NF; exit }' +} + +PORT_GATEWAY=$(echo "${service_env}" | grep ^AGENTTEAMS_PORT_GATEWAY= | cut -d= -f2-) +PORT_CONSOLE=$(echo "${service_env}" | grep ^AGENTTEAMS_PORT_CONSOLE= | cut -d= -f2-) +PORT_GATEWAY="${PORT_GATEWAY:-$(mapped_host_port "${SERVICE_CONTAINER}" 8080)}" +PORT_CONSOLE="${PORT_CONSOLE:-$(mapped_host_port "${SERVICE_CONTAINER}" 8001)}" PORT_GATEWAY="${PORT_GATEWAY:-18080}" PORT_CONSOLE="${PORT_CONSOLE:-18001}" @@ -85,16 +111,16 @@ echo "==> AgentTeams Post-Install Verification" # 1. Manager container running # TODO(k8s): replace with `kubectl get pod -l app=agentteams-manager` and check # that at least one pod is in Running phase (not just Pending/CrashLoopBackOff). -if "${DOCKER_CMD}" ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then +if printf '%s\n' "${running_containers}" | grep -Fxq "${MANAGER_CONTAINER}"; then check_pass "Manager container running" else - check_fail "Manager container running (container '${CONTAINER}' not found in docker ps)" + check_fail "Manager container running (container '${MANAGER_CONTAINER}' not found in ${DOCKER_CMD} ps)" fi -# 2. MinIO health check (internal via docker exec) +# 2. MinIO health check (internal via infrastructure container) # TODO(k8s): replace with `kubectl exec -- curl http://minio..svc:9000/minio/health/live` # or probe the MinIO Service ClusterIP directly if network policy allows. -minio_status=$("${DOCKER_CMD}" exec "${CONTAINER}" \ +minio_status=$("${DOCKER_CMD}" exec "${SERVICE_CONTAINER}" \ curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ "http://127.0.0.1:9000/minio/health/live" 2>/dev/null) || minio_status="000" if [ "${minio_status}" = "200" ]; then @@ -103,9 +129,9 @@ else check_fail "MinIO health check (HTTP ${minio_status})" fi -# 3. Matrix API reachable (internal via docker exec) +# 3. Matrix API reachable (internal via infrastructure container) # TODO(k8s): replace with `kubectl exec -- curl http://matrix..svc:6167/_matrix/client/versions` -matrix_status=$("${DOCKER_CMD}" exec "${CONTAINER}" \ +matrix_status=$("${DOCKER_CMD}" exec "${SERVICE_CONTAINER}" \ curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ "http://127.0.0.1:6167/_matrix/client/versions" 2>/dev/null) || matrix_status="000" if [ "${matrix_status}" = "200" ]; then @@ -139,12 +165,12 @@ fi # 6. Manager Agent healthy (runtime-aware check) # TODO(k8s): replace with `kubectl exec -- ` # Pod name must be resolved dynamically before this call. -MANAGER_RUNTIME=$(echo "$container_env" | grep ^AGENTTEAMS_MANAGER_RUNTIME= | cut -d= -f2-) +MANAGER_RUNTIME=$(echo "${manager_env}" | grep ^AGENTTEAMS_MANAGER_RUNTIME= | cut -d= -f2-) MANAGER_RUNTIME="${MANAGER_RUNTIME:-openclaw}" if [ "${MANAGER_RUNTIME}" = "copaw" ]; then # CoPaw: check app API health endpoint - agent_status=$("${DOCKER_CMD}" exec "${CONTAINER}" \ + agent_status=$("${DOCKER_CMD}" exec "${MANAGER_CONTAINER}" \ curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ "http://127.0.0.1:18799/health" 2>/dev/null) || agent_status="000" if [ "${agent_status}" = "200" ]; then @@ -154,7 +180,7 @@ if [ "${MANAGER_RUNTIME}" = "copaw" ]; then fi else # OpenClaw: check gateway health - agent_output=$("${DOCKER_CMD}" exec "${CONTAINER}" \ + agent_output=$("${DOCKER_CMD}" exec "${MANAGER_CONTAINER}" \ openclaw gateway health --json 2>/dev/null) || agent_output="" if echo "${agent_output}" | grep -q '"ok"'; then check_pass "OpenClaw Agent healthy" diff --git a/install/tests/test-verify-container-routing.sh b/install/tests/test-verify-container-routing.sh new file mode 100755 index 000000000..115f7dfd6 --- /dev/null +++ b/install/tests/test-verify-container-routing.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +VERIFY_SCRIPT="${ROOT_DIR}/install/hiclaw-verify.sh" +TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/agentteams-verify-routing.XXXXXX")" +trap 'rm -rf "${TEST_ROOT}"' EXIT + +FAKE_BIN="${TEST_ROOT}/bin" +CAPTURE="${TEST_ROOT}/exec-calls" +mkdir -p "${FAKE_BIN}" + +cat > "${FAKE_BIN}/docker" <<'EOF' +#!/usr/bin/env bash +set -e + +mode="${VERIFY_FAKE_MODE:?}" +case "${1:-}" in + version) + exit 0 + ;; + ps) + if [ "${mode}" = "embedded" ]; then + printf '%s\n' agentteams-controller agentteams-manager + else + printf '%s\n' agentteams-manager + fi + exit 0 + ;; + port) + container="${2:-}" + container_port="${3:-}" + printf 'port %s %s\n' "${container}" "${container_port}" >> "${VERIFY_FAKE_CAPTURE}" + case "${container_port}" in + 8080/tcp) printf '127.0.0.1:19080\n' ;; + 8001/tcp) printf '127.0.0.1:19001\n' ;; + *) exit 1 ;; + esac + exit 0 + ;; + exec) + container="${2:-}" + command="${3:-}" + case "${command}" in + printenv) + if [ "${container}" = "agentteams-manager" ]; then + printf 'AGENTTEAMS_MANAGER_RUNTIME=copaw\n' + if [ "${mode}" = "legacy" ]; then + printf 'AGENTTEAMS_PORT_GATEWAY=18080\n' + printf 'AGENTTEAMS_PORT_CONSOLE=18001\n' + fi + elif [ "${container}" = "agentteams-controller" ]; then + : + fi + exit 0 + ;; + curl) + url="${!#}" + printf '%s %s\n' "${container}" "${url}" >> "${VERIFY_FAKE_CAPTURE}" + case "${url}" in + *:9000/minio/health/live|*:6167/_matrix/client/versions) + if { [ "${mode}" = "embedded" ] && [ "${container}" = "agentteams-controller" ]; } || \ + { [ "${mode}" = "legacy" ] && [ "${container}" = "agentteams-manager" ]; }; then + printf '200' + else + printf '000' + fi + ;; + *:18799/health) + if [ "${container}" = "agentteams-manager" ]; then + printf '200' + else + printf '000' + fi + ;; + *) + printf '000' + ;; + esac + exit 0 + ;; + esac + ;; +esac + +printf 'unexpected docker invocation: %s\n' "$*" >&2 +exit 1 +EOF + +cat > "${FAKE_BIN}/curl" <<'EOF' +#!/usr/bin/env bash +printf 'host %s\n' "${!#}" >> "${VERIFY_FAKE_CAPTURE}" +printf '200' +EOF + +chmod +x "${FAKE_BIN}/docker" "${FAKE_BIN}/curl" + +PASS=0 +FAIL=0 +RUN_OUTPUT="" +RUN_RC=0 + +pass() { + printf 'PASS: %s\n' "$1" + PASS=$((PASS + 1)) +} + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + FAIL=$((FAIL + 1)) +} + +run_verify() { + local mode="$1" + rm -f "${CAPTURE}" + set +e + RUN_OUTPUT="$( + env \ + PATH="${FAKE_BIN}:${PATH}" \ + VERIFY_FAKE_MODE="${mode}" \ + VERIFY_FAKE_CAPTURE="${CAPTURE}" \ + AGENTTEAMS_VERIFY_INFRA_CONTAINER=agentteams-controller \ + bash "${VERIFY_SCRIPT}" 2>&1 + )" + RUN_RC=$? + set -e +} + +run_verify embedded +if [ "${RUN_RC}" -eq 0 ] \ + && [[ "${RUN_OUTPUT}" == *"Result: 6/6 passed"* ]] \ + && grep -Fq 'agentteams-controller http://127.0.0.1:9000/minio/health/live' "${CAPTURE}" \ + && grep -Fq 'agentteams-controller http://127.0.0.1:6167/_matrix/client/versions' "${CAPTURE}" \ + && grep -Fq 'agentteams-manager http://127.0.0.1:18799/health' "${CAPTURE}" \ + && grep -Fq 'host http://127.0.0.1:19080/' "${CAPTURE}" \ + && grep -Fq 'host http://127.0.0.1:19001/' "${CAPTURE}"; then + pass "embedded verification routes infrastructure and Agent checks correctly" +else + fail "embedded verification routing: rc=${RUN_RC}, output=${RUN_OUTPUT}" +fi + +run_verify legacy +if [ "${RUN_RC}" -eq 0 ] \ + && [[ "${RUN_OUTPUT}" == *"Result: 6/6 passed"* ]] \ + && grep -Fq 'agentteams-manager http://127.0.0.1:9000/minio/health/live' "${CAPTURE}" \ + && grep -Fq 'agentteams-manager http://127.0.0.1:6167/_matrix/client/versions' "${CAPTURE}" \ + && ! grep -Fq 'agentteams-controller ' "${CAPTURE}"; then + pass "legacy verification keeps infrastructure checks in the Manager container" +else + fail "legacy verification routing: rc=${RUN_RC}, output=${RUN_OUTPUT}" +fi + +printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}" +if [ "${FAIL}" -ne 0 ]; then + exit 1 +fi