fix(docker): use authenticated gateway health command (#118996)

* fix(docker): use authenticated gateway health command

* test(docker): prove documented compose health command
This commit is contained in:
Vincent Koc
2026-08-04 07:10:36 +08:00
committed by GitHub
parent a78f2c40bf
commit 15b23cfc2d
5 changed files with 62 additions and 5 deletions
+1 -1
View File
@@ -287,7 +287,7 @@ The image's built-in `HEALTHCHECK` pings `/healthz`; repeated failures mark the
Authenticated deep health snapshot:
```bash
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"
docker compose exec openclaw-gateway sh -lc 'node dist/index.js gateway health --token "$OPENCLAW_GATEWAY_TOKEN"'
```
### LAN vs loopback
+1 -1
View File
@@ -985,4 +985,4 @@ echo "Token: stored in Docker environment/config (not printed)."
echo ""
echo "Commands:"
echo " ${COMPOSE_HINT} logs -f openclaw-gateway"
echo " ${COMPOSE_HINT} exec openclaw-gateway sh -lc 'node dist/index.js health --token \"\$OPENCLAW_GATEWAY_TOKEN\"'"
echo " ${COMPOSE_HINT} exec openclaw-gateway sh -lc 'node dist/index.js gateway health --token \"\$OPENCLAW_GATEWAY_TOKEN\"'"
+33 -3
View File
@@ -17,10 +17,35 @@ cleanup() {
docker_e2e_docker_cmd rm -f "$CLI_NAME" >/dev/null 2>&1 || true
"${COMPOSE[@]}" down --remove-orphans --volumes >/dev/null 2>&1 || true
docker_e2e_cleanup_package_tgz "$PACKAGE_TGZ"
docker_e2e_docker_cmd run --rm --user 0:0 \
-v "$PROJECT_DIR:/target" \
"$IMAGE_NAME" \
sh -c 'rm -rf /target/* /target/.[!.]* /target/..?*' >/dev/null 2>&1 || true
rm -rf "$PROJECT_DIR"
}
trap cleanup EXIT
assert_gateway_health_json() {
local label="$1"
local health_path="$2"
node - "$label" "$health_path" <<'NODE'
const fs = require("node:fs");
const label = process.argv[2];
const healthPath = process.argv[3];
const health = JSON.parse(fs.readFileSync(healthPath, "utf8"));
if (
health?.ok !== true ||
!Number.isFinite(health.ts) ||
!Number.isFinite(health.durationMs) ||
!health.channels ||
typeof health.channels !== "object" ||
Array.isArray(health.channels)
) {
throw new Error(`${label} gateway health JSON is incomplete`);
}
NODE
}
mkdir -p "$PROJECT_DIR/config/workspace" "$PROJECT_DIR/auth-profile"
chmod -R 0777 "$PROJECT_DIR/config" "$PROJECT_DIR/auth-profile"
cat >"$PROJECT_DIR/config/openclaw.json" <<EOF
@@ -71,8 +96,11 @@ if [ "$(docker inspect --format '{{.State.Health.Status}}' "$GATEWAY_ID")" != "h
exit 1
fi
"${COMPOSE[@]}" exec -T openclaw-gateway node dist/index.js health --token "$TOKEN"
"${COMPOSE[@]}" run -T --no-deps --name "$CLI_NAME" openclaw-cli health --token "$TOKEN"
"${COMPOSE[@]}" exec -T openclaw-gateway sh -lc 'node dist/index.js gateway health --token "$OPENCLAW_GATEWAY_TOKEN"'
"${COMPOSE[@]}" exec -T openclaw-gateway node dist/index.js gateway health --token "$TOKEN" --json >"$PROJECT_DIR/gateway-health.json"
assert_gateway_health_json "gateway service" "$PROJECT_DIR/gateway-health.json"
"${COMPOSE[@]}" run -T --no-deps --name "$CLI_NAME" openclaw-cli gateway health --token "$TOKEN" --json >"$PROJECT_DIR/cli-health.json"
assert_gateway_health_json "CLI sidecar" "$PROJECT_DIR/cli-health.json"
GATEWAY_VERSION="$("${COMPOSE[@]}" exec -T openclaw-gateway node -p "require('./package.json').version")"
node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts" \
@@ -84,6 +112,8 @@ node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identit
--container "cli=$CLI_NAME" \
--detail "gateway:openclawVersion=$GATEWAY_VERSION" \
--detail "gateway:health=healthy" \
--detail "cli:healthCommand=passed"
--detail "gateway:documentedHealthCommand=passed" \
--detail "gateway:healthJsonEnvelope=passed" \
--detail "cli:healthJsonEnvelope=passed"
echo "Docker Compose setup proof passed."
+4
View File
@@ -155,6 +155,10 @@ describe("scripts/docker/setup.sh", () => {
expect(result.stdout).toContain("Access from tailnet devices via the host's tailnet IP.");
expect(result.stdout).toContain("Commands:");
expect(result.stdout).toContain("logs -f openclaw-gateway");
expect(result.stdout).toContain(
`exec openclaw-gateway sh -lc 'node dist/index.js gateway health --token "$OPENCLAW_GATEWAY_TOKEN"'`,
);
expect(result.stdout).not.toContain("node dist/index.js health --token");
expect(result.stdout).not.toContain("test-token");
expect(result.stdout).not.toContain("#token=");
expect(log).toContain(
+23
View File
@@ -10,6 +10,7 @@ const repoRoot = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
const dockerfilePath = join(repoRoot, "Dockerfile");
const dockerComposePath = join(repoRoot, "docker-compose.yml");
const dockerInstallDocsPath = join(repoRoot, "docs/install/docker.md");
const composeSetupScriptPath = join(repoRoot, "scripts/e2e/compose-setup.sh");
const dockerReleaseWorkflowPath = join(repoRoot, ".github/workflows/docker-release.yml");
const fullReleaseValidationWorkflowPath = join(
repoRoot,
@@ -46,6 +47,28 @@ describe("Dockerfile", () => {
expect(compose).not.toContain("127.0.0.1:18789/healthz");
});
it("executes the documented Compose health command and validates JSON envelopes", async () => {
const docs = await readFile(dockerInstallDocsPath, "utf8");
const composeSetup = await readFile(composeSetupScriptPath, "utf8");
const gatewayHealthCommand =
'node dist/index.js gateway health --token "$OPENCLAW_GATEWAY_TOKEN"';
expect(docs).toContain(`docker compose exec openclaw-gateway sh -lc '${gatewayHealthCommand}'`);
expect(docs).not.toContain('node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"');
expect(composeSetup).toContain(
`"\${COMPOSE[@]}" exec -T openclaw-gateway sh -lc '${gatewayHealthCommand}'`,
);
expect(composeSetup.match(/gateway health --token "\$TOKEN" --json/g)).toHaveLength(2);
expect(composeSetup).toContain('assert_gateway_health_json "gateway service"');
expect(composeSetup).toContain('assert_gateway_health_json "CLI sidecar"');
expect(composeSetup).toContain('--detail "gateway:documentedHealthCommand=passed"');
expect(composeSetup).toContain('--detail "gateway:healthJsonEnvelope=passed"');
expect(composeSetup).toContain('--detail "cli:healthJsonEnvelope=passed"');
expect(composeSetup).not.toContain('dist/index.js health --token "$TOKEN"');
expect(composeSetup).toContain('-v "$PROJECT_DIR:/target"');
expect(composeSetup).toContain("rm -rf /target/* /target/.[!.]* /target/..?*");
});
it("does not force an external Dockerfile frontend pull", async () => {
for (const path of dockerSetupDockerfilePaths) {
const dockerfile = await readFile(join(repoRoot, path), "utf8");