Skip to content

feat(openclaw): additive Docker integration with env-driven hardening + doctor cleanup#649

Open
nnnet wants to merge 1 commit into
builderz-labs:mainfrom
nnnet:pr/openclaw-integration
Open

feat(openclaw): additive Docker integration with env-driven hardening + doctor cleanup#649
nnnet wants to merge 1 commit into
builderz-labs:mainfrom
nnnet:pr/openclaw-integration

Conversation

@nnnet
Copy link
Copy Markdown

@nnnet nnnet commented May 5, 2026

Summary

Adds an additive OpenClaw gateway integration: a separate docker-compose-openclaw.yml that stands up the gateway daemon alongside Mission Control plus a small CLI compatibility shim, env-driven hardening, doctor-banner cleanup, and a brew-enabled sandbox image. The integration is non-invasive — when this compose file isn't included, MC keeps working through its direct-dispatch path (PR #648). When it is included, MC discovers the gateway with no extra configuration.

Headline pieces

Live-updateable gateway

openclaw-src/ is bind-mounted from the host clone into the gateway container, so git pull && pnpm build refreshes the daemon without rebuilding the image.

CLI compatibility shim (scripts/openclaw-cli-shim.py)

  • Rewrites the legacy CLI shapes MC's source still uses (gateway sessions_send …) into the modern RPC call shape (gateway call sessions_send --params {…}). MC stays unmodified; openclaw stays read-only.
  • Projects an env-driven security posture into ~/.openclaw/openclaw.json on every invocation: OPENCLAW_SECURITY_WORKSPACE_ONLY, OPENCLAW_SECURITY_DENY_AUTOMATION, OPENCLAW_SECURITY_DENY_RUNTIME, OPENCLAW_SECURITY_DENY_FS, OPENCLAW_SECURITY_SANDBOX_ALL, TELEGRAM_*. Reproducible via .env, no manual JSON edits.
  • Filters two openclaw doctor output quirks that surface as a warning banner in MC even when nothing is wrong:
    • The unconditional footer Run "openclaw doctor --fix" to apply changes. (printed whenever --fix is absent, regardless of whether anything is fixable — see openclaw-src flows/doctor-health-contributions.ts:580-582). The substring fix was tripping MC's mentionsWarnings regex.
    • The Plugins panel's Errors: 0. The substring error (case-insensitive, no word boundary) was tripping MC's level-error escalation regex. Rewritten to Errs: 0 only when the count is zero; real Errors: N>0 counts pass through unchanged so genuine plugin errors still raise the banner.

Path-equivalent state dir

Gateway-issued docker bind-mounts now resolve identically inside the gateway container and on the host (so sandbox containers actually see the workspace).

Auto-pair + Control UI

  • scripts/openclaw-auto-pair.py writes the pairing identity into the gateway's state directory directly, removing a 12-step manual onboarding.
  • scripts/openclaw-auto-approve-control-ui.mjs keeps the local-dev pairing approval flow auto-clicking.
  • The Control UI runs at http://127.0.0.1:18791/ behind an nginx that strips loopback-pairing friction and forwards WebSocket upgrades correctly (docker/openclaw-control-ui.nginx.conf).

Brew-enabled gateway and sandbox images

  • Dockerfile.openclaw.dockercli overlays Linuxbrew on node:24-bookworm so the skills.install RPC can brew install <formula> for skills declaring brew dependencies. Also ships the docker CLI so the gateway can spawn sandbox containers via the host docker socket.
  • Dockerfile.openclaw.sandbox overlays brew on top of the upstream openclaw-sandbox:bookworm-slim base (kept separate from openclaw-src/ to preserve the read-only update flow via make openclaw-update).

MC dev container reaches the host docker daemon

  • docker.io added to Dockerfile.dev so openclaw doctor can verify sandbox readiness without emitting "Sandbox mode is enabled but Docker is not available" to the doctor panel.
  • /var/run/docker.sock bind-mounted; uid 1000 reaches the socket via group_add driven by a new DOCKER_SOCKET_GID env, auto-detected by the Makefile via stat -c %g /var/run/docker.sock (defaults to 994 for stock Debian/Ubuntu hosts; manually overridable on Fedora/Arch/colima/Rancher Desktop where the gid differs).
  • An empty stub file (.docker-mask/openclaw-stub.empty) is bind-mounted over ${HOME}/.openclaw so openclaw doctor's findOtherStateDirs() no longer trips "Multiple state directories detected" against host state visible through the broad ${HOME}:${HOME} bind. The path becomes a regular file inside the container, so existsDir() returns false and the dir is skipped.

Why this is useful upstream

OpenClaw is a macOS-only app today. This makes it usable on any Linux host (cloud VMs, dev sandboxes) without forking either codebase. The shim, env-driven hardening, and doctor-banner filters are deliberately external — neither MC nor openclaw-src is modified. Operators can git clone, set .env, and make to bring up an end-to-end MC+gateway stack.

Test plan

  1. make → all containers healthy: mission-control-dev, mc-openclaw-gateway, mc-openclaw-control-ui, mc-openclaw-control-ui-autopair.
  2. docker exec mission-control-dev docker version → returns the host docker daemon version.
  3. docker exec mission-control-dev openclaw doctor → no Run "openclaw doctor --fix" footer; Errors: 0 rendered as Errs: 0; no Multiple state directories detected.
  4. Open http://127.0.0.1:7012/agents → no doctor warning banner.
  5. Open http://127.0.0.1:18791/ → Control UI loads; pairing auto-approves.
  6. Dispatch a sandboxed task (e.g. weather skill) end-to-end → succeeds.

Provenance

This PR squashes the in-flight history into one cohesive change. Original commits (preserved in our fork at nnnet/mission-control:experiment/openclaw-integration):

  • cdd75c7 feat(openclaw): additive Docker integration for the OpenClaw gateway
  • e205819 experiment(openclaw): bake openclaw CLI + GATEWAY env into MC dev image
  • c0e71d2 experiment(openclaw): config-only auto-pair flow + agent id binding
  • 702eb23 experiment(openclaw): live-updateable bind-mount layout, no docker rebuilds for upgrades
  • beab916 fix(experiment/openclaw): browser WebSocket URL + agent select usability
  • fa28bea fix(experiment/openclaw): rewrite retired CLI shapes via bind-mounted shim
  • 73b76e7 fix(experiment/openclaw): silence browser WS retry + fix Command tab Send
  • cc2aa74 Fix prod Mission Control OpenClaw linkage and gateway startup warnings
  • 6b1708c fix: harden prod OpenClaw linkage and expose dedicated control UI
  • fd585c1 fix: route OpenClaw control UI through WS-safe proxy
  • 6213efe fix(openclaw): bootstrap control UI localhost origin allowlist
  • 119ec22 Fix local OpenClaw Control UI pairing flow
  • 72d14cf fix(openclaw): stabilize control-ui pairing and telegram allowlist bootstrap
  • c470da4 refactor make/openclaw startup to env-driven runtime config
  • 7c83423 fix(openclaw): path-equivalent state dir; stop env_file override in compose
  • b2a75fe/2434bad chore(openclaw): harden gateway and add lmstudio/openai models
  • f1c6aed Make OpenClaw tool posture env-driven
  • 7a1e819 fix(openclaw): enforce env-driven hardening defaults
  • 5f996f1 revert(openclaw): remove doctor info suppression toggle
  • 1c24c51 feat(openclaw): make Telegram DM policy env-driven
  • dc2e381 fix(openclaw): project telegram owner policy in MC shim state
  • d0ddc19 chore(openclaw): prebuild dockercli image and run gateway as node
  • b8f7dfe chore: allow auto_backup default via env
  • 62daf0a feat(docker): enable docker-in-container + suppress spurious doctor banner

Dependencies

… + doctor cleanup

## Summary

Adds an additive OpenClaw gateway integration: a separate `docker-compose-openclaw.yml` that stands up the gateway daemon alongside Mission Control plus a small CLI compatibility shim, env-driven hardening, doctor-banner cleanup, and a brew-enabled sandbox image. The integration is **non-invasive** — when this compose file isn't included, MC keeps working through its direct-dispatch path (PR builderz-labs#648). When it is included, MC discovers the gateway with no extra configuration.

## Headline pieces

### Live-updateable gateway
`openclaw-src/` is bind-mounted from the host clone into the gateway container, so `git pull && pnpm build` refreshes the daemon without rebuilding the image.

### CLI compatibility shim (`scripts/openclaw-cli-shim.py`)
- Rewrites the legacy CLI shapes MC's source still uses (`gateway sessions_send …`) into the modern RPC call shape (`gateway call sessions_send --params {…}`). MC stays unmodified; openclaw stays read-only.
- Projects an env-driven security posture into `~/.openclaw/openclaw.json` on every invocation: `OPENCLAW_SECURITY_WORKSPACE_ONLY`, `OPENCLAW_SECURITY_DENY_AUTOMATION`, `OPENCLAW_SECURITY_DENY_RUNTIME`, `OPENCLAW_SECURITY_DENY_FS`, `OPENCLAW_SECURITY_SANDBOX_ALL`, `TELEGRAM_*`. Reproducible via `.env`, no manual JSON edits.
- Filters two `openclaw doctor` output quirks that surface as a warning banner in MC even when nothing is wrong:
  - The unconditional footer `Run "openclaw doctor --fix" to apply changes.` (printed whenever `--fix` is absent, regardless of whether anything is fixable — see `openclaw-src` `flows/doctor-health-contributions.ts:580-582`). The substring `fix` was tripping MC's `mentionsWarnings` regex.
  - The Plugins panel's `Errors: 0`. The substring `error` (case-insensitive, no word boundary) was tripping MC's level-`error` escalation regex. Rewritten to `Errs: 0` only when the count is zero; real `Errors: N>0` counts pass through unchanged so genuine plugin errors still raise the banner.

### Path-equivalent state dir
Gateway-issued docker bind-mounts now resolve identically inside the gateway container and on the host (so sandbox containers actually see the workspace).

### Auto-pair + Control UI
- `scripts/openclaw-auto-pair.py` writes the pairing identity into the gateway's state directory directly, removing a 12-step manual onboarding.
- `scripts/openclaw-auto-approve-control-ui.mjs` keeps the local-dev pairing approval flow auto-clicking.
- The Control UI runs at `http://127.0.0.1:18791/` behind an nginx that strips loopback-pairing friction and forwards WebSocket upgrades correctly (`docker/openclaw-control-ui.nginx.conf`).

### Brew-enabled gateway and sandbox images
- `Dockerfile.openclaw.dockercli` overlays Linuxbrew on `node:24-bookworm` so the `skills.install` RPC can `brew install <formula>` for skills declaring brew dependencies. Also ships the docker CLI so the gateway can spawn sandbox containers via the host docker socket.
- `Dockerfile.openclaw.sandbox` overlays brew on top of the upstream `openclaw-sandbox:bookworm-slim` base (kept separate from `openclaw-src/` to preserve the read-only update flow via `make openclaw-update`).

### MC dev container reaches the host docker daemon
- `docker.io` added to `Dockerfile.dev` so `openclaw doctor` can verify sandbox readiness without emitting "Sandbox mode is enabled but Docker is not available" to the doctor panel.
- `/var/run/docker.sock` bind-mounted; uid 1000 reaches the socket via `group_add` driven by a new `DOCKER_SOCKET_GID` env, auto-detected by the Makefile via `stat -c %g /var/run/docker.sock` (defaults to 994 for stock Debian/Ubuntu hosts; manually overridable on Fedora/Arch/colima/Rancher Desktop where the gid differs).
- An empty stub file (`.docker-mask/openclaw-stub.empty`) is bind-mounted over `${HOME}/.openclaw` so openclaw doctor's `findOtherStateDirs()` no longer trips "Multiple state directories detected" against host state visible through the broad `${HOME}:${HOME}` bind. The path becomes a regular file inside the container, so `existsDir()` returns false and the dir is skipped.

## Why this is useful upstream

OpenClaw is a macOS-only app today. This makes it usable on any Linux host (cloud VMs, dev sandboxes) without forking either codebase. The shim, env-driven hardening, and doctor-banner filters are **deliberately external** — neither MC nor openclaw-src is modified. Operators can `git clone`, set `.env`, and `make` to bring up an end-to-end MC+gateway stack.

## Test plan

1. `make` → all containers healthy: `mission-control-dev`, `mc-openclaw-gateway`, `mc-openclaw-control-ui`, `mc-openclaw-control-ui-autopair`.
2. `docker exec mission-control-dev docker version` → returns the host docker daemon version.
3. `docker exec mission-control-dev openclaw doctor` → no `Run "openclaw doctor --fix"` footer; `Errors: 0` rendered as `Errs: 0`; no `Multiple state directories detected`.
4. Open `http://127.0.0.1:7012/agents` → no doctor warning banner.
5. Open `http://127.0.0.1:18791/` → Control UI loads; pairing auto-approves.
6. Dispatch a sandboxed task (e.g. weather skill) end-to-end → succeeds.

## Provenance

This PR squashes the in-flight history into one cohesive change. Original commits (preserved in our fork at `nnnet/mission-control:experiment/openclaw-integration`):

- `cdd75c7 feat(openclaw): additive Docker integration for the OpenClaw gateway`
- `e205819 experiment(openclaw): bake openclaw CLI + GATEWAY env into MC dev image`
- `c0e71d2 experiment(openclaw): config-only auto-pair flow + agent id binding`
- `702eb23 experiment(openclaw): live-updateable bind-mount layout, no docker rebuilds for upgrades`
- `beab916 fix(experiment/openclaw): browser WebSocket URL + agent select usability`
- `fa28bea fix(experiment/openclaw): rewrite retired CLI shapes via bind-mounted shim`
- `73b76e7 fix(experiment/openclaw): silence browser WS retry + fix Command tab Send`
- `cc2aa74 Fix prod Mission Control OpenClaw linkage and gateway startup warnings`
- `6b1708c fix: harden prod OpenClaw linkage and expose dedicated control UI`
- `fd585c1 fix: route OpenClaw control UI through WS-safe proxy`
- `6213efe fix(openclaw): bootstrap control UI localhost origin allowlist`
- `119ec22 Fix local OpenClaw Control UI pairing flow`
- `72d14cf fix(openclaw): stabilize control-ui pairing and telegram allowlist bootstrap`
- `c470da4 refactor make/openclaw startup to env-driven runtime config`
- `7c83423 fix(openclaw): path-equivalent state dir; stop env_file override in compose`
- `b2a75fe`/`2434bad chore(openclaw): harden gateway and add lmstudio/openai models`
- `f1c6aed Make OpenClaw tool posture env-driven`
- `7a1e819 fix(openclaw): enforce env-driven hardening defaults`
- `5f996f1 revert(openclaw): remove doctor info suppression toggle`
- `1c24c51 feat(openclaw): make Telegram DM policy env-driven`
- `dc2e381 fix(openclaw): project telegram owner policy in MC shim state`
- `d0ddc19 chore(openclaw): prebuild dockercli image and run gateway as node`
- `b8f7dfe chore: allow auto_backup default via env`
- `62daf0a feat(docker): enable docker-in-container + suppress spurious doctor banner`

## Dependencies

- Conceptually builds on **builderz-labs#646** (Docker stack) and **builderz-labs#648** (direct multi-provider dispatch), but is independently mergeable: when applied alone on top of `main`, the openclaw-specific files are net-new and the changes to `Dockerfile.dev`/`docker-compose-dev.yml`/`Makefile` are additive.
- Reviewers comfortable merging stacked PRs: please consider landing builderz-labs#646builderz-labs#648 → this one in that order; this PR will pick up clean rebase deltas afterwards.
Copy link
Copy Markdown

@0xbrainkid 0xbrainkid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Blocking: the OpenClaw compose integration bakes a host-specific absolute path into the committed compose file, so it will not work on other operator machines.

Strengths

  • The doctor unit tests cover several warning-cleanup paths and passed locally.
  • The integration is nicely documented and keeps OpenClaw additive rather than forcing MC to depend on it.

Issues

  • [BLOCKING] docker-compose-openclaw.yml hard-codes /mnt/9/gt/rig_PlatformsAI/mayor/rig/beads/discovered/mission-control/.openclaw-data into OPENCLAW_STATE_DIR, OPENCLAW_PLUGIN_STAGE_DIR, OPENCLAW_CONFIG_PATH, and the state volume mount. On any clone outside that exact host path, the gateway will write config/state under a non-existent or wrong host path and sandbox bind mounts will not line up. Please derive this from a configurable env var (for example MC_PROJECT_ROOT/OPENCLAW_HOST_STATE_DIR) with a documented default, or avoid the path-equivalence requirement in the committed default.

Questions

  • Can the Makefile/bootstrap generate the absolute path into a local ignored override file instead of committing a machine-specific path?

Verification

  • gh pr checks 649: no checks reported.
  • pnpm vitest run src/lib/__tests__/openclaw-doctor.test.ts: passed (10 tests).
  • docker compose -f docker-compose-openclaw.yml config --quiet: passed syntax/config rendering, but does not catch the portability issue above.

@0xNyk
Copy link
Copy Markdown
Member

0xNyk commented May 7, 2026

Thanks — this is genuinely well-engineered work. Real coverage of an important deployment surface (operator runs MC + OpenClaw stack on a Linux host with sandbox-skill execution), good security-conscious defaults (cap_drop: ALL, no-new-privileges, runs as non-root node for runtime, OPENCLAW_SECURITY_* hardening flags, OPENCLAW_SECURITY_WORKSPACE_ONLY=1 default), and the auto-pair scripts have proper validation (isPrivateOrLoopbackIp, clientId === 'openclaw-control-ui', existing-pubkey-mismatch rejection).

But I can't merge it as a single 23-file / 2,924-LoC PR — too large to land safely against the "only merge focused, non-breaking changes" bar I'm using on this triage pass.

Three concerns I'd want addressed first:

1. Overlap with #646 and #650

docker-compose.yml, docker-compose-dev.yml, Dockerfile, Dockerfile.dev, Makefile, .gitignore, .env.example, .dockerignore — all overlap with #646. docker-compose-openclaw.yml overlaps with #650. Could you confirm whether:

If sequencing: please rebase #649 onto whatever order makes sense and submit as the delta on top of the previous one. Right now gh pr diff on each shows a lot of duplicated content because each is branched off main.

2. Privilege-model documentation

The /var/run/docker.sock:rw bind-mount on openclaw-gateway (necessary for sandbox skill execution, I get it) is root-on-host equivalent for anything that lands in that container. The hardening flags reduce blast radius but don't eliminate it.

docs/deployment.md has the new sections but the privilege model warning is buried. Could you add a short "Security model" subsection up top, before the make openclaw-up instructions, that says:

Enabling the OpenClaw stack via make openclaw-up mounts /var/run/docker.sock into the gateway container. This is required for the sandbox-skill execution model (the gateway spawns nested Docker containers per skill), but it gives anything running inside the gateway container privileges equivalent to root on the host. Operators should treat the gateway container as a privileged workload and avoid running it on hosts where root-equivalent access would be unacceptable. Not recommended for shared/multi-tenant Linux hosts.

That way operators reading the README understand the tradeoff before they enable the flag.

3. Three external-script-fetch patterns at build time

Dockerfile.openclaw.sandbox and Dockerfile.openclaw.dockercli both fetch and execute Homebrew's installer from raw.githubusercontent.com/Homebrew/install. docker-compose-openclaw.yml's openclaw-builder service fetches and executes Bun's installer from bun.sh/install.

These are well-known projects with canonical install patterns, so it's defensible — but it does mean any future compromise of either CDN injects code into the build. If you'd like to be belt-and-suspenders here, pinning a specific commit/tag of the Homebrew installer (raw.githubusercontent.com/Homebrew/install/<sha>/install.sh) and checksumming the Bun installer would close the gap. Not blocking, but worth considering.

Path forward

Happy to merge once (1) the relationship with #646/#650 is sorted, and (2) the privilege-model doc section is added. Tagging as needs-changes rather than closing — this is good work and I want it to land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants