fix(docker): land #33097 from @chengzhichao-xydt

Landed from contributor PR #33097 by @chengzhichao-xydt.

Co-authored-by: Zhichao Cheng <cheng.zhichao@xydigit.com>
This commit is contained in:
Peter Steinberger
2026-03-07 23:41:35 +00:00
parent 2fc95a7cfc
commit ada4ee08d9
4 changed files with 86 additions and 5 deletions

View File

@@ -306,6 +306,7 @@ Docs: https://docs.openclaw.ai
- Nodes/system.run allow-always persistence: honor shell comment semantics during allowlist analysis so `#`-tailed payloads that never execute are not persisted as trusted follow-up commands. Thanks @tdjackey for reporting.
- Signal/inbound attachment fan-in: forward all successfully fetched inbound attachments through `MediaPaths`/`MediaUrls`/`MediaTypes` (instead of only the first), and improve multi-attachment placeholder summaries in mention-gated pending history. (#39212) Thanks @joeykrug.
- Nodes/system.run dispatch-wrapper boundary: keep shell-wrapper approval classification active at the depth boundary so `env` wrapper stacks cannot reach `/bin/sh -c` execution without the expected approval gate. Thanks @tdjackey for reporting.
- Docker/token persistence on reconfigure: reuse the existing `.env` gateway token during `docker-setup.sh` reruns and align compose token env defaults, so Docker installs stop silently rotating tokens and breaking existing dashboard sessions. Landed from contributor PR #33097 by @chengzhichao-xydt. Thanks @chengzhichao-xydt.
## 2026.3.2

View File

@@ -4,7 +4,7 @@ services:
environment:
HOME: /home/node
TERM: xterm-256color
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:-}
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: ${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
@@ -59,7 +59,7 @@ services:
environment:
HOME: /home/node
TERM: xterm-256color
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:-}
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: ${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}
BROWSER: echo
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}

View File

@@ -80,6 +80,24 @@ NODE
fi
}
read_env_gateway_token() {
local env_path="$1"
local line=""
local token=""
if [[ ! -f "$env_path" ]]; then
return 0
fi
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
if [[ "$line" == OPENCLAW_GATEWAY_TOKEN=* ]]; then
token="${line#OPENCLAW_GATEWAY_TOKEN=}"
fi
done <"$env_path"
if [[ -n "$token" ]]; then
printf '%s' "$token"
fi
}
ensure_control_ui_allowed_origins() {
if [[ "${OPENCLAW_GATEWAY_BIND}" == "loopback" ]]; then
return 0
@@ -219,14 +237,20 @@ if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
if [[ -n "$EXISTING_CONFIG_TOKEN" ]]; then
OPENCLAW_GATEWAY_TOKEN="$EXISTING_CONFIG_TOKEN"
echo "Reusing gateway token from $OPENCLAW_CONFIG_DIR/openclaw.json"
elif command -v openssl >/dev/null 2>&1; then
OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)"
else
OPENCLAW_GATEWAY_TOKEN="$(python3 - <<'PY'
DOTENV_GATEWAY_TOKEN="$(read_env_gateway_token "$ROOT_DIR/.env" || true)"
if [[ -n "$DOTENV_GATEWAY_TOKEN" ]]; then
OPENCLAW_GATEWAY_TOKEN="$DOTENV_GATEWAY_TOKEN"
echo "Reusing gateway token from $ROOT_DIR/.env"
elif command -v openssl >/dev/null 2>&1; then
OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)"
else
OPENCLAW_GATEWAY_TOKEN="$(python3 - <<'PY'
import secrets
print(secrets.token_hex(32))
PY
)"
fi
fi
fi
export OPENCLAW_GATEWAY_TOKEN

View File

@@ -250,6 +250,55 @@ describe("docker-setup.sh", () => {
expect(envFile).toContain("OPENCLAW_GATEWAY_TOKEN=config-token-123"); // pragma: allowlist secret
});
it("reuses existing .env token when OPENCLAW_GATEWAY_TOKEN and config token are unset", async () => {
const activeSandbox = requireSandbox(sandbox);
const configDir = join(activeSandbox.rootDir, "config-dotenv-token-reuse");
const workspaceDir = join(activeSandbox.rootDir, "workspace-dotenv-token-reuse");
await mkdir(configDir, { recursive: true });
await writeFile(
join(activeSandbox.rootDir, ".env"),
"OPENCLAW_GATEWAY_TOKEN=dotenv-token-123\nOPENCLAW_GATEWAY_PORT=18789\n",
);
const result = runDockerSetup(activeSandbox, {
OPENCLAW_GATEWAY_TOKEN: undefined,
OPENCLAW_CONFIG_DIR: configDir,
OPENCLAW_WORKSPACE_DIR: workspaceDir,
});
expect(result.status).toBe(0);
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
expect(envFile).toContain("OPENCLAW_GATEWAY_TOKEN=dotenv-token-123"); // pragma: allowlist secret
expect(result.stderr).toBe("");
});
it("reuses the last non-empty .env token and strips CRLF without truncating '='", async () => {
const activeSandbox = requireSandbox(sandbox);
const configDir = join(activeSandbox.rootDir, "config-dotenv-last-wins");
const workspaceDir = join(activeSandbox.rootDir, "workspace-dotenv-last-wins");
await mkdir(configDir, { recursive: true });
await writeFile(
join(activeSandbox.rootDir, ".env"),
[
"OPENCLAW_GATEWAY_TOKEN=",
"OPENCLAW_GATEWAY_TOKEN=first-token",
"OPENCLAW_GATEWAY_TOKEN=last=token=value\r",
].join("\n"),
);
const result = runDockerSetup(activeSandbox, {
OPENCLAW_GATEWAY_TOKEN: undefined,
OPENCLAW_CONFIG_DIR: configDir,
OPENCLAW_WORKSPACE_DIR: workspaceDir,
});
expect(result.status).toBe(0);
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
expect(envFile).toContain("OPENCLAW_GATEWAY_TOKEN=last=token=value"); // pragma: allowlist secret
expect(envFile).not.toContain("OPENCLAW_GATEWAY_TOKEN=first-token");
expect(envFile).not.toContain("\r");
});
it("treats OPENCLAW_SANDBOX=0 as disabled", async () => {
const activeSandbox = requireSandbox(sandbox);
await writeFile(activeSandbox.logPath, "");
@@ -399,4 +448,11 @@ describe("docker-setup.sh", () => {
expect(compose).toContain('network_mode: "service:openclaw-gateway"');
expect(compose).toContain("depends_on:\n - openclaw-gateway");
});
it("keeps docker-compose gateway token env defaults aligned across services", async () => {
const compose = await readFile(join(repoRoot, "docker-compose.yml"), "utf8");
expect(compose.match(/OPENCLAW_GATEWAY_TOKEN: \$\{OPENCLAW_GATEWAY_TOKEN:-\}/g)).toHaveLength(
2,
);
});
});