Skip to content

Commit 900bc71

Browse files
yasinBursaliclaude
andcommitted
fix(openclaw): trigger open-webui recreate on install; simplify volume layout
The openclaw extension bundles an open-webui overlay that adds OPENAI_API_BASE_URLS + OPENAI_API_KEYS to register openclaw as a model provider. The install path uses `docker compose up -d --no-deps openclaw`, so the overlay env never reached the already-running open-webui container — the "openclaw in Open WebUI" feature silently broke until a manual `dream restart`. Adds a minimal _post_install_core_recreate helper in the host agent that recreates open-webui after a successful openclaw install. Helper is a no-op for other services; hardcoded rather than manifest-driven to keep scope tight. Wrapped in try/except so install success isn't blocked by a recreate failure (the progress write happens first). Also simplifies the openclaw volume layout. The openclaw-home named volume held only regenerated content (openclaw.json is rebuilt by inject-token.js on every container start) plus incidental caches — verified by inspecting the openclaw image filesystem. Removing it eliminates a named-volume/bind-mount overlap at /home/node/.openclaw/, simplifies the compose, and introduces zero data-loss risk for fresh installs. Existing installs: the orphaned openclaw-home volume can be reclaimed with `docker volume rm openclaw-home` — contents are non-critical. Phase 06 directory-setup comment updated to reflect the new behavior. Also drops a stale Volume Mounts row in openclaw/README.md that referenced a bind that never existed in compose.yaml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 97906f8 commit 900bc71

5 files changed

Lines changed: 117 additions & 8 deletions

File tree

dream-server/bin/dream-host-agent.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,29 @@ def docker_compose_recreate(service_ids: list[str]) -> tuple:
341341
return False, f"Docker compose operation timed out ({SUBPROCESS_TIMEOUT_START}s)"
342342

343343

344+
def _post_install_core_recreate(service_id: str) -> None:
345+
"""Force-recreate core services whose env was overridden by ``service_id``'s
346+
compose.yaml overlay.
347+
348+
``docker compose up -d --no-deps <ext>`` (how _handle_install starts the
349+
extension) will not pick up overlay changes targeting already-running core
350+
services. openclaw's compose.yaml appends an OPENAI_API_BASE_URLS entry to
351+
open-webui; without this post-install recreate that overlay is silently
352+
ignored until the next core restart.
353+
354+
Failure is logged and swallowed — the extension itself is already running;
355+
the overlay will apply on the next manual restart of the core service.
356+
"""
357+
if service_id != "openclaw":
358+
return
359+
ok, err = docker_compose_recreate(["open-webui"])
360+
if not ok:
361+
logger.warning(
362+
"Post-install recreate of open-webui failed after openclaw install: %s",
363+
err,
364+
)
365+
366+
344367
def _parse_mem_value(s: str) -> float:
345368
"""Parse Docker memory string like '256MiB' or '4GiB' to MB."""
346369
s = s.strip()
@@ -1232,6 +1255,18 @@ def _run_install():
12321255
# Step 4: Success
12331256
_write_progress(service_id, "started", "Service started")
12341257

1258+
# Step 5: Post-install core recreate (best-effort, non-fatal).
1259+
# Some extensions (e.g. openclaw) add overlay env to already-
1260+
# running core services; `up -d --no-deps <ext>` won't apply
1261+
# those changes. Failure here must not fail the install.
1262+
try:
1263+
_post_install_core_recreate(service_id)
1264+
except Exception:
1265+
logger.exception(
1266+
"Post-install core recreate raised for %s (ignored)",
1267+
service_id,
1268+
)
1269+
12351270
except subprocess.TimeoutExpired:
12361271
_write_progress(service_id, "error", "Installation failed",
12371272
error=f"timed out ({SUBPROCESS_TIMEOUT_START}s)")

dream-server/extensions/services/dashboard-api/tests/test_host_agent.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
resolve_compose_flags = _mod.resolve_compose_flags
2323
validate_core_recreate_ids = _mod.validate_core_recreate_ids
2424
invalidate_compose_cache = _mod.invalidate_compose_cache
25+
_post_install_core_recreate = _mod._post_install_core_recreate
2526

2627

2728
# --- _parse_mem_value ---
@@ -321,6 +322,83 @@ def test_docker_compose_recreate_still_uses_no_deps(self):
321322
)
322323

323324

325+
# --- _post_install_core_recreate ---
326+
#
327+
# openclaw's compose.yaml adds OPENAI_API_BASE_URLS to open-webui as an overlay;
328+
# `docker compose up -d --no-deps openclaw` (used by _handle_install) won't
329+
# pick up overlay changes targeting already-running core services. Hence the
330+
# post-install recreate of open-webui whenever openclaw is installed.
331+
332+
333+
class TestPostInstallCoreRecreate:
334+
335+
def test_openclaw_triggers_open_webui_recreate(self, monkeypatch):
336+
calls = []
337+
338+
def _fake_recreate(ids):
339+
calls.append(list(ids))
340+
return True, ""
341+
342+
monkeypatch.setattr(_mod, "docker_compose_recreate", _fake_recreate)
343+
_post_install_core_recreate("openclaw")
344+
assert calls == [["open-webui"]]
345+
346+
def test_non_openclaw_service_is_noop(self, monkeypatch):
347+
calls = []
348+
349+
def _fake_recreate(ids):
350+
calls.append(list(ids))
351+
return True, ""
352+
353+
monkeypatch.setattr(_mod, "docker_compose_recreate", _fake_recreate)
354+
for svc in ("litellm", "n8n", "perplexica", "whisper", "comfyui"):
355+
_post_install_core_recreate(svc)
356+
assert calls == []
357+
358+
def test_recreate_failure_is_swallowed(self, monkeypatch):
359+
"""Install must not fail if the post-install recreate errors — openclaw
360+
is already running; the overlay just won't take effect until a manual
361+
core restart."""
362+
363+
def _fake_recreate(_ids):
364+
return False, "docker compose exploded"
365+
366+
monkeypatch.setattr(_mod, "docker_compose_recreate", _fake_recreate)
367+
# Must not raise
368+
_post_install_core_recreate("openclaw")
369+
370+
371+
class TestRunInstallCallsPostInstallRecreate:
372+
"""Source-level check that the install closure calls
373+
_post_install_core_recreate after the "started" progress write.
374+
375+
The dynamic flow runs in a daemon thread + nested closure, which makes
376+
runtime mocking fragile (see TestInstallHookEnvAllowlist for the same
377+
reasoning). Source-level assertion is sufficient to lock the wiring."""
378+
379+
def _install_source(self):
380+
import inspect
381+
return inspect.getsource(_mod.AgentHandler._handle_install)
382+
383+
def test_install_calls_post_install_core_recreate(self):
384+
src = self._install_source()
385+
assert "_post_install_core_recreate(service_id)" in src, (
386+
"_run_install must invoke _post_install_core_recreate(service_id) "
387+
"after emitting the 'started' progress record"
388+
)
389+
390+
def test_recreate_is_after_started_progress_write(self):
391+
src = self._install_source()
392+
started_idx = src.find('"started"')
393+
recreate_idx = src.find("_post_install_core_recreate(")
394+
assert started_idx != -1, "expected 'started' progress write in _handle_install"
395+
assert recreate_idx != -1, "expected _post_install_core_recreate call in _handle_install"
396+
assert started_idx < recreate_idx, (
397+
"_post_install_core_recreate must run AFTER the 'started' progress "
398+
"write so the client sees success even if the recreate fails"
399+
)
400+
401+
324402
# --- _handle_env_update ---
325403

326404

dream-server/extensions/services/openclaw/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ Environment variables (set in `.env`):
4444
|-----------|---------------|---------|
4545
| `./config/openclaw` | `/config` (read-only) | Gateway config files and token injection script |
4646
| `./data/openclaw` | `/data` | Agent task state and persistent data |
47-
| `./data/openclaw/home` | `/home/node/.openclaw` | OpenClaw user home directory |
4847
| `./config/openclaw/workspace` | `/home/node/.openclaw/workspace` | Agent workspace directory |
4948

5049
## Architecture

dream-server/extensions/services/openclaw/compose.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ services:
2424
volumes:
2525
- ./config/openclaw:/config:ro
2626
- ./data/openclaw:/data
27-
- openclaw-home:/home/node/.openclaw
2827
- ./config/openclaw/workspace:/home/node/.openclaw/workspace
2928
ports:
3029
- "${BIND_ADDRESS:-127.0.0.1}:${OPENCLAW_PORT:-7860}:18789"
@@ -57,6 +56,3 @@ services:
5756
environment:
5857
- OPENAI_API_BASE_URLS=${LLM_API_URL:-http://llama-server:8080}/v1;http://openclaw:18790/v1
5958
- OPENAI_API_KEYS=;${OPENCLAW_TOKEN:-}
60-
61-
volumes:
62-
openclaw-home:

dream-server/installers/phases/06-directories.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,10 @@ Fix with: sudo chown -R \$(id -u):\$(id -g) $INSTALL_DIR/config $INSTALL_DIR/dat
149149
log "Installed OpenClaw config: $OPENCLAW_CONFIG -> openclaw.json (model: $OPENCLAW_MODEL)"
150150
# Generate OPENCLAW_TOKEN (used by compose env and inject-token.js)
151151
OPENCLAW_TOKEN=$(openssl rand -hex 24 2>/dev/null || head -c 24 /dev/urandom | xxd -p)
152-
# Note: OpenClaw home dir uses a named Docker volume (openclaw-home).
153-
# inject-token.js patches the runtime config at container startup,
154-
# so we don't seed files into the volume from the installer.
152+
# Note: inject-token.js regenerates /home/node/.openclaw/openclaw.json
153+
# on every container start — that path lives in the container's ephemeral
154+
# overlay, so no installer seeding is needed. Only workspace/ is persisted,
155+
# via the bind mount at ./config/openclaw/workspace (see below).
155156
# Create workspace directory (must exist before Docker Compose,
156157
# otherwise Docker auto-creates it as root and the container can't write to it)
157158
mkdir -p "$INSTALL_DIR/config/openclaw/workspace/memory"

0 commit comments

Comments
 (0)