mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 12:18:37 +00:00
fix: harden Docker/GCP onboarding flow (#26253) (thanks @pandego)
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Docker/GCP onboarding: reduce first-build OOM risk by capping Node heap during `pnpm install`, reuse existing gateway token during `docker-setup.sh` reruns so `.env` stays aligned with config, auto-bootstrap Control UI allowed origins for non-loopback Docker binds, and add GCP docs guidance for tokenized dashboard links + pairing recovery commands. (#26253) Thanks @pandego.
|
||||||
- Agents/Subagents delivery: refactor subagent completion announce dispatch into an explicit queue/direct/fallback state machine, recover outbound channel-plugin resolution in cold/stale plugin-registry states across announce/message/gateway send paths, finalize cleanup bookkeeping when announce flow rejects, and treat Telegram sends without `message_id` as delivery failures (instead of false-success `"unknown"` IDs). (#26867, #25961, #26803, #25069, #26741) Thanks @SmithLabsLLC and @docaohieu2808.
|
- Agents/Subagents delivery: refactor subagent completion announce dispatch into an explicit queue/direct/fallback state machine, recover outbound channel-plugin resolution in cold/stale plugin-registry states across announce/message/gateway send paths, finalize cleanup bookkeeping when announce flow rejects, and treat Telegram sends without `message_id` as delivery failures (instead of false-success `"unknown"` IDs). (#26867, #25961, #26803, #25069, #26741) Thanks @SmithLabsLLC and @docaohieu2808.
|
||||||
- Telegram/Webhook: pre-initialize webhook bots, switch webhook processing to callback-mode JSON handling, and preserve full near-limit payload reads under delayed handlers to prevent webhook request hangs and dropped updates. (#26156)
|
- Telegram/Webhook: pre-initialize webhook bots, switch webhook processing to callback-mode JSON handling, and preserve full near-limit payload reads under delayed handlers to prevent webhook request hangs and dropped updates. (#26156)
|
||||||
- Slack/Session threads: prevent oversized parent-session inheritance from silently bricking new thread sessions, surface embedded context-overflow empty-result failures to users, and add configurable `session.parentForkMaxTokens` (default `100000`, `0` disables). (#26912) Thanks @markshields-tl.
|
- Slack/Session threads: prevent oversized parent-session inheritance from silently bricking new thread sessions, surface embedded context-overflow empty-result failures to users, and add configurable `session.parentForkMaxTokens` (default `100000`, `0` disables). (#26912) Thanks @markshields-tl.
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ COPY --chown=node:node scripts ./scripts
|
|||||||
USER node
|
USER node
|
||||||
# Reduce OOM risk on low-memory hosts during dependency installation.
|
# Reduce OOM risk on low-memory hosts during dependency installation.
|
||||||
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
||||||
ENV NODE_OPTIONS=--max-old-space-size=2048
|
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||||
RUN pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
# Optionally install Chromium and Xvfb for browser automation.
|
# Optionally install Chromium and Xvfb for browser automation.
|
||||||
# Build with: docker build --build-arg OPENCLAW_INSTALL_BROWSER=1 ...
|
# Build with: docker build --build-arg OPENCLAW_INSTALL_BROWSER=1 ...
|
||||||
|
|||||||
@@ -20,6 +20,78 @@ require_cmd() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
read_config_gateway_token() {
|
||||||
|
local config_path="$OPENCLAW_CONFIG_DIR/openclaw.json"
|
||||||
|
if [[ ! -f "$config_path" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$config_path" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
gateway = cfg.get("gateway")
|
||||||
|
if not isinstance(gateway, dict):
|
||||||
|
raise SystemExit(0)
|
||||||
|
auth = gateway.get("auth")
|
||||||
|
if not isinstance(auth, dict):
|
||||||
|
raise SystemExit(0)
|
||||||
|
token = auth.get("token")
|
||||||
|
if isinstance(token, str):
|
||||||
|
token = token.strip()
|
||||||
|
if token:
|
||||||
|
print(token)
|
||||||
|
PY
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
node - "$config_path" <<'NODE'
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const configPath = process.argv[2];
|
||||||
|
try {
|
||||||
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||||
|
const token = cfg?.gateway?.auth?.token;
|
||||||
|
if (typeof token === "string" && token.trim().length > 0) {
|
||||||
|
process.stdout.write(token.trim());
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep docker-setup resilient when config parsing fails.
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_control_ui_allowed_origins() {
|
||||||
|
if [[ "${OPENCLAW_GATEWAY_BIND}" == "loopback" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local allowed_origin_json
|
||||||
|
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
|
||||||
|
)"
|
||||||
|
current_allowed_origins="${current_allowed_origins//$'\r'/}"
|
||||||
|
|
||||||
|
if [[ -n "$current_allowed_origins" && "$current_allowed_origins" != "null" && "$current_allowed_origins" != "[]" ]]; then
|
||||||
|
echo "Control UI allowlist already configured; leaving gateway.controlUi.allowedOrigins unchanged."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-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."
|
||||||
|
}
|
||||||
|
|
||||||
contains_disallowed_chars() {
|
contains_disallowed_chars() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
||||||
@@ -97,7 +169,11 @@ export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
|
|||||||
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
|
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
|
||||||
|
|
||||||
if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
|
if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
|
||||||
if command -v openssl >/dev/null 2>&1; then
|
EXISTING_CONFIG_TOKEN="$(read_config_gateway_token || true)"
|
||||||
|
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)"
|
OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)"
|
||||||
else
|
else
|
||||||
OPENCLAW_GATEWAY_TOKEN="$(python3 - <<'PY'
|
OPENCLAW_GATEWAY_TOKEN="$(python3 - <<'PY'
|
||||||
@@ -273,6 +349,10 @@ echo " - Install Gateway daemon: No"
|
|||||||
echo ""
|
echo ""
|
||||||
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli onboard --no-install-daemon
|
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli onboard --no-install-daemon
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Control UI origin allowlist"
|
||||||
|
ensure_control_ui_allowed_origins
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Provider setup (optional)"
|
echo "==> Provider setup (optional)"
|
||||||
echo "WhatsApp (QR):"
|
echo "WhatsApp (QR):"
|
||||||
|
|||||||
@@ -353,6 +353,14 @@ docker compose up -d openclaw-gateway
|
|||||||
|
|
||||||
If build fails with `Killed` / `exit code 137` during `pnpm install --frozen-lockfile`, the VM is out of memory. Use `e2-small` minimum, or `e2-medium` for more reliable first builds.
|
If build fails with `Killed` / `exit code 137` during `pnpm install --frozen-lockfile`, the VM is out of memory. Use `e2-small` minimum, or `e2-medium` for more reliable first builds.
|
||||||
|
|
||||||
|
When binding to LAN (`OPENCLAW_GATEWAY_BIND=lan`), configure a trusted browser origin before continuing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm openclaw-cli config set gateway.controlUi.allowedOrigins '["http://127.0.0.1:18789"]' --strict-json
|
||||||
|
```
|
||||||
|
|
||||||
|
If you changed the gateway port, replace `18789` with your configured port.
|
||||||
|
|
||||||
Verify binaries:
|
Verify binaries:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -397,7 +405,20 @@ Open in your browser:
|
|||||||
|
|
||||||
`http://127.0.0.1:18789/`
|
`http://127.0.0.1:18789/`
|
||||||
|
|
||||||
Paste your gateway token.
|
Fetch a fresh tokenized dashboard link:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm openclaw-cli dashboard --no-open
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste the token from that URL.
|
||||||
|
|
||||||
|
If Control UI shows `unauthorized` or `disconnected (1008): pairing required`, approve the browser device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm openclaw-cli devices list
|
||||||
|
docker compose run --rm openclaw-cli devices approve <requestId>
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,27 @@ describe("docker-setup.sh", () => {
|
|||||||
expect(identityDirStat.isDirectory()).toBe(true);
|
expect(identityDirStat.isDirectory()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reuses existing config token when OPENCLAW_GATEWAY_TOKEN is unset", async () => {
|
||||||
|
const activeSandbox = requireSandbox(sandbox);
|
||||||
|
const configDir = join(activeSandbox.rootDir, "config-token-reuse");
|
||||||
|
const workspaceDir = join(activeSandbox.rootDir, "workspace-token-reuse");
|
||||||
|
await mkdir(configDir, { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
join(configDir, "openclaw.json"),
|
||||||
|
JSON.stringify({ gateway: { auth: { mode: "token", token: "config-token-123" } } }),
|
||||||
|
);
|
||||||
|
|
||||||
|
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=config-token-123");
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects injected multiline OPENCLAW_EXTRA_MOUNTS values", async () => {
|
it("rejects injected multiline OPENCLAW_EXTRA_MOUNTS values", async () => {
|
||||||
const activeSandbox = requireSandbox(sandbox);
|
const activeSandbox = requireSandbox(sandbox);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const dockerfilePath = join(repoRoot, "Dockerfile");
|
|||||||
describe("Dockerfile", () => {
|
describe("Dockerfile", () => {
|
||||||
it("installs optional browser dependencies after pnpm install", async () => {
|
it("installs optional browser dependencies after pnpm install", async () => {
|
||||||
const dockerfile = await readFile(dockerfilePath, "utf8");
|
const dockerfile = await readFile(dockerfilePath, "utf8");
|
||||||
const installIndex = dockerfile.indexOf("RUN pnpm install --frozen-lockfile");
|
const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile");
|
||||||
const browserArgIndex = dockerfile.indexOf("ARG OPENCLAW_INSTALL_BROWSER");
|
const browserArgIndex = dockerfile.indexOf("ARG OPENCLAW_INSTALL_BROWSER");
|
||||||
|
|
||||||
expect(installIndex).toBeGreaterThan(-1);
|
expect(installIndex).toBeGreaterThan(-1);
|
||||||
|
|||||||
Reference in New Issue
Block a user