From dbb806d2574cd8aadf11646795833360e320a888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=B1=84=EC=99=84?= Date: Tue, 24 Mar 2026 13:26:01 +0900 Subject: [PATCH] Docker: avoid setup CLI namespace loop --- scripts/docker/setup.sh | 26 ++++++++++++++++---------- src/docker-setup.e2e.test.ts | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/scripts/docker/setup.sh b/scripts/docker/setup.sh index cfa6fd4046e..216edde38a9 100755 --- a/scripts/docker/setup.sh +++ b/scripts/docker/setup.sh @@ -108,8 +108,7 @@ ensure_control_ui_allowed_origins() { local current_allowed_origins allowed_origin_json="$(printf '["http://127.0.0.1:%s"]' "$OPENCLAW_GATEWAY_PORT")" current_allowed_origins="$( - docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \ - config get gateway.controlUi.allowedOrigins 2>/dev/null || true + run_setup_cli config get gateway.controlUi.allowedOrigins 2>/dev/null || true )" current_allowed_origins="${current_allowed_origins//$'\r'/}" @@ -118,19 +117,26 @@ ensure_control_ui_allowed_origins() { return 0 fi - docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \ - config set gateway.controlUi.allowedOrigins "$allowed_origin_json" --strict-json >/dev/null + run_setup_cli config set gateway.controlUi.allowedOrigins "$allowed_origin_json" --strict-json \ + >/dev/null echo "Set gateway.controlUi.allowedOrigins to $allowed_origin_json for non-loopback bind." } sync_gateway_mode_and_bind() { - docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \ - config set gateway.mode local >/dev/null - docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \ - config set gateway.bind "$OPENCLAW_GATEWAY_BIND" >/dev/null + run_setup_cli config set gateway.mode local >/dev/null + run_setup_cli config set gateway.bind "$OPENCLAW_GATEWAY_BIND" >/dev/null echo "Pinned gateway.mode=local and gateway.bind=$OPENCLAW_GATEWAY_BIND for Docker setup." } +run_setup_cli() { + # During setup, avoid the shared-network openclaw-cli service because it + # requires the gateway container's network namespace to already exist. That + # creates a circular dependency for config writes that are needed before the + # gateway can start cleanly. + docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps --entrypoint node openclaw-gateway \ + dist/index.js "$@" +} + contains_disallowed_chars() { local value="$1" [[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]] @@ -458,7 +464,7 @@ echo "==> Fixing data-directory permissions" # ownership of all user project files on Linux hosts. # After fixing the config dir, only the OpenClaw metadata subdirectory # (.openclaw/) inside the workspace gets chowned, not the user's project files. -docker compose "${COMPOSE_ARGS[@]}" run --rm --user root --entrypoint sh openclaw-cli -c \ +docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps --user root --entrypoint sh openclaw-gateway -c \ 'find /home/node/.openclaw -xdev -exec chown node:node {} +; \ [ -d /home/node/.openclaw/workspace/.openclaw ] && chown -R node:node /home/node/.openclaw/workspace/.openclaw || true' @@ -471,7 +477,7 @@ echo "Gateway token: $OPENCLAW_GATEWAY_TOKEN" echo "Tailscale exposure: Off (use host-level tailnet/Tailscale setup separately)." echo "Install Gateway daemon: No (managed by Docker Compose)" echo "" -docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli onboard --mode local --no-install-daemon +run_setup_cli onboard --mode local --no-install-daemon echo "" echo "==> Docker gateway defaults" diff --git a/src/docker-setup.e2e.test.ts b/src/docker-setup.e2e.test.ts index 04b3823388f..9c2a7ebe06b 100644 --- a/src/docker-setup.e2e.test.ts +++ b/src/docker-setup.e2e.test.ts @@ -206,9 +206,37 @@ describe("scripts/docker/setup.sh", () => { expect(extraCompose).toContain("openclaw-home:"); const log = await readFile(activeSandbox.logPath, "utf8"); expect(log).toContain("--build-arg OPENCLAW_DOCKER_APT_PACKAGES=ffmpeg build-essential"); - expect(log).toContain("run --rm openclaw-cli onboard --mode local --no-install-daemon"); - expect(log).toContain("run --rm openclaw-cli config set gateway.mode local"); - expect(log).toContain("run --rm openclaw-cli config set gateway.bind lan"); + expect(log).toContain( + "run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js onboard --mode local --no-install-daemon", + ); + expect(log).toContain( + "run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.mode local", + ); + expect(log).toContain( + "run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.bind lan", + ); + expect(log).toContain( + 'run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.controlUi.allowedOrigins ["http://127.0.0.1:18789"] --strict-json', + ); + expect(log).not.toContain("run --rm openclaw-cli onboard --mode local --no-install-daemon"); + }); + + it("avoids shared-network openclaw-cli before the gateway is started", async () => { + const activeSandbox = requireSandbox(sandbox); + + const result = runDockerSetup(activeSandbox); + expect(result.status).toBe(0); + + const log = await readFile(activeSandbox.logPath, "utf8"); + const lines = log.split("\n").filter(Boolean); + const gatewayStartIdx = lines.findIndex( + (line) => + line.includes("compose") && line.includes(" up -d") && line.includes("openclaw-gateway"), + ); + expect(gatewayStartIdx).toBeGreaterThanOrEqual(0); + + const prestartLines = lines.slice(0, gatewayStartIdx); + expect(prestartLines.some((line) => line.includes("run --rm openclaw-cli"))).toBe(false); }); it("precreates config identity dir for CLI device auth writes", async () => { @@ -260,6 +288,7 @@ describe("scripts/docker/setup.sh", () => { const onboardIdx = log.indexOf("onboard"); expect(chownIdx).toBeGreaterThanOrEqual(0); expect(onboardIdx).toBeGreaterThan(chownIdx); + expect(log).toContain("run --rm --no-deps --user root --entrypoint sh openclaw-gateway -c"); }); it("reuses existing config token when OPENCLAW_GATEWAY_TOKEN is unset", async () => {