mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-26 17:32:16 +00:00
refactor: clarify docker setup cli phases
This commit is contained in:
@@ -55,6 +55,10 @@ Docker is **optional**. Use it only if you want a containerized gateway or to va
|
||||
- generate a gateway token and write it to `.env`
|
||||
- start the gateway via Docker Compose
|
||||
|
||||
During setup, pre-start onboarding and config writes run through
|
||||
`openclaw-gateway` directly. `openclaw-cli` is for commands you run after
|
||||
the gateway container already exists.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Open the Control UI">
|
||||
@@ -94,7 +98,15 @@ If you prefer to run each step yourself instead of using the setup script:
|
||||
|
||||
```bash
|
||||
docker build -t openclaw:local -f Dockerfile .
|
||||
docker compose run --rm openclaw-cli onboard
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
|
||||
dist/index.js onboard --mode local --no-install-daemon
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
|
||||
dist/index.js config set gateway.mode local
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
|
||||
dist/index.js config set gateway.bind lan
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
|
||||
dist/index.js config set gateway.controlUi.allowedOrigins \
|
||||
'["http://localhost:18789","http://127.0.0.1:18789"]' --strict-json
|
||||
docker compose up -d openclaw-gateway
|
||||
```
|
||||
|
||||
@@ -104,6 +116,13 @@ or `OPENCLAW_HOME_VOLUME`, the setup script writes `docker-compose.extra.yml`;
|
||||
include it with `-f docker-compose.yml -f docker-compose.extra.yml`.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
Because `openclaw-cli` shares `openclaw-gateway`'s network namespace, it is a
|
||||
post-start tool. Before `docker compose up -d openclaw-gateway`, run onboarding
|
||||
and setup-time config writes through `openclaw-gateway` with
|
||||
`--no-deps --entrypoint node`.
|
||||
</Note>
|
||||
|
||||
### Environment variables
|
||||
|
||||
The setup script accepts these optional environment variables:
|
||||
|
||||
@@ -108,7 +108,7 @@ ensure_control_ui_allowed_origins() {
|
||||
local current_allowed_origins
|
||||
allowed_origin_json="$(printf '["http://localhost:%s","http://127.0.0.1:%s"]' "$OPENCLAW_GATEWAY_PORT" "$OPENCLAW_GATEWAY_PORT")"
|
||||
current_allowed_origins="$(
|
||||
run_setup_cli config get gateway.controlUi.allowedOrigins 2>/dev/null || true
|
||||
run_prestart_cli config get gateway.controlUi.allowedOrigins 2>/dev/null || true
|
||||
)"
|
||||
current_allowed_origins="${current_allowed_origins//$'\r'/}"
|
||||
|
||||
@@ -117,26 +117,53 @@ ensure_control_ui_allowed_origins() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
run_setup_cli config set gateway.controlUi.allowedOrigins "$allowed_origin_json" --strict-json \
|
||||
run_prestart_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() {
|
||||
run_setup_cli config set gateway.mode local >/dev/null
|
||||
run_setup_cli config set gateway.bind "$OPENCLAW_GATEWAY_BIND" >/dev/null
|
||||
run_prestart_cli config set gateway.mode local >/dev/null
|
||||
run_prestart_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() {
|
||||
run_prestart_gateway() {
|
||||
docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps "$@"
|
||||
}
|
||||
|
||||
run_prestart_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 \
|
||||
run_prestart_gateway --entrypoint node openclaw-gateway \
|
||||
dist/index.js "$@"
|
||||
}
|
||||
|
||||
run_runtime_cli() {
|
||||
local compose_scope="${1:-current}"
|
||||
local deps_mode="${2:-with-deps}"
|
||||
shift 2
|
||||
|
||||
local -a compose_args
|
||||
local -a run_args=(run --rm)
|
||||
|
||||
case "$compose_scope" in
|
||||
current) compose_args=("${COMPOSE_ARGS[@]}") ;;
|
||||
base) compose_args=("${BASE_COMPOSE_ARGS[@]}") ;;
|
||||
*) fail "Unknown runtime CLI compose scope: $compose_scope" ;;
|
||||
esac
|
||||
|
||||
case "$deps_mode" in
|
||||
with-deps) ;;
|
||||
no-deps) run_args+=(--no-deps) ;;
|
||||
*) fail "Unknown runtime CLI deps mode: $deps_mode" ;;
|
||||
esac
|
||||
|
||||
docker compose "${compose_args[@]}" "${run_args[@]}" openclaw-cli "$@"
|
||||
}
|
||||
|
||||
contains_disallowed_chars() {
|
||||
local value="$1"
|
||||
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
||||
@@ -464,7 +491,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 --no-deps --user root --entrypoint sh openclaw-gateway -c \
|
||||
run_prestart_gateway --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'
|
||||
|
||||
@@ -477,7 +504,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 ""
|
||||
run_setup_cli onboard --mode local --no-install-daemon
|
||||
run_prestart_cli onboard --mode local --no-install-daemon
|
||||
|
||||
echo ""
|
||||
echo "==> Docker gateway defaults"
|
||||
@@ -561,17 +588,17 @@ fi
|
||||
if [[ -n "$SANDBOX_ENABLED" ]]; then
|
||||
# Enable sandbox in OpenClaw config.
|
||||
sandbox_config_ok=true
|
||||
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
|
||||
if ! run_runtime_cli current no-deps \
|
||||
config set agents.defaults.sandbox.mode "non-main" >/dev/null; then
|
||||
echo "WARNING: Failed to set agents.defaults.sandbox.mode" >&2
|
||||
sandbox_config_ok=false
|
||||
fi
|
||||
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
|
||||
if ! run_runtime_cli current no-deps \
|
||||
config set agents.defaults.sandbox.scope "agent" >/dev/null; then
|
||||
echo "WARNING: Failed to set agents.defaults.sandbox.scope" >&2
|
||||
sandbox_config_ok=false
|
||||
fi
|
||||
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
|
||||
if ! run_runtime_cli current no-deps \
|
||||
config set agents.defaults.sandbox.workspaceAccess "none" >/dev/null; then
|
||||
echo "WARNING: Failed to set agents.defaults.sandbox.workspaceAccess" >&2
|
||||
sandbox_config_ok=false
|
||||
@@ -585,7 +612,7 @@ if [[ -n "$SANDBOX_ENABLED" ]]; then
|
||||
else
|
||||
echo "WARNING: Sandbox config was partially applied. Check errors above." >&2
|
||||
echo " Skipping gateway restart to avoid exposing Docker socket without a full sandbox policy." >&2
|
||||
if ! docker compose "${BASE_COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
|
||||
if ! run_runtime_cli base no-deps \
|
||||
config set agents.defaults.sandbox.mode "off" >/dev/null; then
|
||||
echo "WARNING: Failed to roll back agents.defaults.sandbox.mode to off" >&2
|
||||
else
|
||||
@@ -601,7 +628,7 @@ else
|
||||
# Keep reruns deterministic: if sandbox is not active for this run, reset
|
||||
# persisted sandbox mode so future execs do not require docker.sock by stale
|
||||
# config alone.
|
||||
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \
|
||||
if ! run_runtime_cli current with-deps \
|
||||
config set agents.defaults.sandbox.mode "off" >/dev/null; then
|
||||
echo "WARNING: Failed to reset agents.defaults.sandbox.mode to off" >&2
|
||||
fi
|
||||
|
||||
@@ -114,6 +114,26 @@ function runDockerSetup(
|
||||
});
|
||||
}
|
||||
|
||||
async function resetDockerLog(sandbox: DockerSetupSandbox) {
|
||||
await writeFile(sandbox.logPath, "");
|
||||
}
|
||||
|
||||
async function readDockerLog(sandbox: DockerSetupSandbox) {
|
||||
return readFile(sandbox.logPath, "utf8");
|
||||
}
|
||||
|
||||
async function readDockerLogLines(sandbox: DockerSetupSandbox) {
|
||||
return (await readDockerLog(sandbox)).split("\n").filter(Boolean);
|
||||
}
|
||||
|
||||
function isGatewayStartLine(line: string) {
|
||||
return line.includes("compose") && line.includes(" up -d") && line.includes("openclaw-gateway");
|
||||
}
|
||||
|
||||
function findGatewayStartLineIndex(lines: string[]) {
|
||||
return lines.findIndex((line) => isGatewayStartLine(line));
|
||||
}
|
||||
|
||||
async function runDockerSetupWithUnsetGatewayToken(
|
||||
sandbox: DockerSetupSandbox,
|
||||
suffix: string,
|
||||
@@ -204,7 +224,7 @@ describe("scripts/docker/setup.sh", () => {
|
||||
expect(extraCompose).toContain("openclaw-home:/home/node");
|
||||
expect(extraCompose).toContain("volumes:");
|
||||
expect(extraCompose).toContain("openclaw-home:");
|
||||
const log = await readFile(activeSandbox.logPath, "utf8");
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).toContain("--build-arg OPENCLAW_DOCKER_APT_PACKAGES=ffmpeg build-essential");
|
||||
expect(log).toContain(
|
||||
"run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js onboard --mode local --no-install-daemon",
|
||||
@@ -224,16 +244,12 @@ describe("scripts/docker/setup.sh", () => {
|
||||
it("avoids shared-network openclaw-cli before the gateway is started", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
|
||||
await writeFile(activeSandbox.logPath, "");
|
||||
await resetDockerLog(activeSandbox);
|
||||
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"),
|
||||
);
|
||||
const lines = await readDockerLogLines(activeSandbox);
|
||||
const gatewayStartIdx = findGatewayStartLineIndex(lines);
|
||||
expect(gatewayStartIdx).toBeGreaterThanOrEqual(0);
|
||||
|
||||
const prestartLines = lines.slice(0, gatewayStartIdx);
|
||||
@@ -286,7 +302,7 @@ describe("scripts/docker/setup.sh", () => {
|
||||
expect(sessionsDirStat.isDirectory()).toBe(true);
|
||||
|
||||
// Verify that a root-user chown step runs before setup.
|
||||
const log = await readFile(activeSandbox.logPath, "utf8");
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
const chownIdx = log.indexOf("--user root");
|
||||
const onboardIdx = log.indexOf("onboard");
|
||||
expect(chownIdx).toBeGreaterThanOrEqual(0);
|
||||
@@ -350,7 +366,7 @@ describe("scripts/docker/setup.sh", () => {
|
||||
|
||||
it("treats OPENCLAW_SANDBOX=0 as disabled", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await writeFile(activeSandbox.logPath, "");
|
||||
await resetDockerLog(activeSandbox);
|
||||
|
||||
const result = runDockerSetup(activeSandbox, {
|
||||
OPENCLAW_SANDBOX: "0",
|
||||
@@ -360,7 +376,7 @@ describe("scripts/docker/setup.sh", () => {
|
||||
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||
expect(envFile).toContain("OPENCLAW_SANDBOX=");
|
||||
|
||||
const log = await readFile(activeSandbox.logPath, "utf8");
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).toContain("--build-arg OPENCLAW_INSTALL_DOCKER_CLI=");
|
||||
expect(log).not.toContain("--build-arg OPENCLAW_INSTALL_DOCKER_CLI=1");
|
||||
expect(log).toContain("config set agents.defaults.sandbox.mode off");
|
||||
@@ -368,7 +384,7 @@ describe("scripts/docker/setup.sh", () => {
|
||||
|
||||
it("resets stale sandbox mode and overlay when sandbox is not active", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await writeFile(activeSandbox.logPath, "");
|
||||
await resetDockerLog(activeSandbox);
|
||||
await writeFile(
|
||||
join(activeSandbox.rootDir, "docker-compose.sandbox.yml"),
|
||||
"services:\n openclaw-gateway:\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n",
|
||||
@@ -381,14 +397,14 @@ describe("scripts/docker/setup.sh", () => {
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toContain("Sandbox requires Docker CLI");
|
||||
const log = await readFile(activeSandbox.logPath, "utf8");
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).toContain("config set agents.defaults.sandbox.mode off");
|
||||
await expect(stat(join(activeSandbox.rootDir, "docker-compose.sandbox.yml"))).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("skips sandbox gateway restart when sandbox config writes fail", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await writeFile(activeSandbox.logPath, "");
|
||||
await resetDockerLog(activeSandbox);
|
||||
const socketPath = join(activeSandbox.rootDir, "sandbox.sock");
|
||||
|
||||
await withUnixSocket(socketPath, async () => {
|
||||
@@ -402,15 +418,10 @@ describe("scripts/docker/setup.sh", () => {
|
||||
expect(result.stderr).toContain("Failed to set agents.defaults.sandbox.scope");
|
||||
expect(result.stderr).toContain("Skipping gateway restart to avoid exposing Docker socket");
|
||||
|
||||
const log = await readFile(activeSandbox.logPath, "utf8");
|
||||
const gatewayStarts = log
|
||||
.split("\n")
|
||||
.filter(
|
||||
(line) =>
|
||||
line.includes("compose") &&
|
||||
line.includes(" up -d") &&
|
||||
line.includes("openclaw-gateway"),
|
||||
);
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
const gatewayStarts = (await readDockerLogLines(activeSandbox)).filter((line) =>
|
||||
isGatewayStartLine(line),
|
||||
);
|
||||
expect(gatewayStarts).toHaveLength(2);
|
||||
expect(log).toContain(
|
||||
"run --rm --no-deps openclaw-cli config set agents.defaults.sandbox.mode non-main",
|
||||
|
||||
Reference in New Issue
Block a user