From 26806f7d91504d69ce82054b41f28542b071bfab Mon Sep 17 00:00:00 2001 From: namelessman Date: Wed, 18 Mar 2026 16:01:42 +1300 Subject: [PATCH 01/14] feat: add OpenClaw Docker support with configuration and bootstrap script --- .github/workflows/build-docker.yml | 66 +++++++++---- docker_openclaw/README.md | 23 +++++ docker_openclaw/openclaw.Dockerfile | 41 ++++++++ docker_openclaw/work/bootstrap-openclaw.sh | 109 +++++++++++++++++++++ docker_openclaw/work/openclaw.json | 40 ++++++++ 5 files changed, 258 insertions(+), 21 deletions(-) create mode 100644 docker_openclaw/README.md create mode 100644 docker_openclaw/openclaw.Dockerfile create mode 100644 docker_openclaw/work/bootstrap-openclaw.sh create mode 100644 docker_openclaw/work/openclaw.json diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 03c8099..d9b1943 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -2,21 +2,21 @@ name: build-docker-images on: push: - branches: [ "main" ] - paths-ignore: [ "*.md" ] + branches: ["main"] + paths-ignore: ["*.md"] pull_request: - branches: [ "main" ] - paths-ignore: [ "*.md" ] + branches: ["main"] + paths-ignore: ["*.md"] - workflow_dispatch: # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: # Allows you to run this workflow manually from the Actions tab concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: - BUILDKIT_PROGRESS: "plain" # Full logs for CI build. + BUILDKIT_PROGRESS: "plain" # Full logs for CI build. REGISTRY_SRC: ${{ vars.REGISTRY_SRC || 'docker.io' }} # For BASE_NAMESPACE of images: where to pull base images from, docker.io or other source registry URL. REGISTRY_DST: ${{ vars.REGISTRY_DST || 'docker.io' }} # For tags of built images: where to push images to, docker.io or other destination registry URL. # DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_PASSWORD is required for docker image push, they should be set in CI secrets. @@ -30,7 +30,7 @@ env: jobs: ## Clash docker_clash: - name: 'app-clash' + name: "app-clash" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -39,7 +39,7 @@ jobs: ## Casdoor docker_casdoor: - name: 'casdoor' + name: "casdoor" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -48,7 +48,7 @@ jobs: ## Keycloak docker_keycloak: - name: 'keycloak' + name: "keycloak" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -57,7 +57,7 @@ jobs: ## DevHub job-dev-hub: - name: 'dev-hub' + name: "dev-hub" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -70,7 +70,7 @@ jobs: ## OpenResty as gateway job-openresty: - name: 'openresty' + name: "openresty" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -79,7 +79,7 @@ jobs: ## SearchNGX for searching job-searxng: - name: 'searxng' + name: "searxng" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -88,7 +88,7 @@ jobs: ## StoreBox job-storebox: - name: 'storebox' + name: "storebox" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -98,7 +98,7 @@ jobs: ## lognet for log management job-logent: - name: 'logent' + name: "logent" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -108,7 +108,7 @@ jobs: ## nocobase for low-code development platform job-nocobase: - name: 'nocobase' + name: "nocobase" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -116,10 +116,19 @@ jobs: source ./tool.sh build_image nocobase latest docker_nocobase/nocobase.Dockerfile && push_image nocobase + ## OpenClaw + job-openclaw: + name: "openclaw" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - run: | + source ./tool.sh + build_image openclaw latest docker_openclaw/openclaw.Dockerfile && push_image openclaw ## DevBox - base job-base-dev: - name: 'developer,base-dev' + name: "developer,base-dev" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -133,7 +142,7 @@ jobs: ## DevBox - data science stack job-data-science-dev: - name: 'data-science-dev' + name: "data-science-dev" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -148,7 +157,7 @@ jobs: ## DevBox - full stack job-full-stack-dev: - name: 'full-stack-dev' + name: "full-stack-dev" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -163,7 +172,7 @@ jobs: ## DevBox - cuda job-cuda-dev: - name: 'full-cuda,cuda-dev' + name: "full-cuda,cuda-dev" runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -175,10 +184,25 @@ jobs: --build-arg "ARG_PROFILE_VSCODE=base" alias_image cuda-dev latest full-cuda latest && push_image dev - ## Sync all images in this build (listed by "names") to mirror registry. sync_images: - needs: ["job-cuda-dev", "job-data-science-dev", "job-full-stack-dev", "job-base-dev", "job-nocobase", "job-logent", "job-storebox", "job-searxng", "job-openresty", "job-dev-hub", "docker_keycloak", "docker_casdoor", "docker_clash"] + needs: + [ + "job-cuda-dev", + "job-data-science-dev", + "job-full-stack-dev", + "job-base-dev", + "job-nocobase", + "job-openclaw", + "job-logent", + "job-storebox", + "job-searxng", + "job-openresty", + "job-dev-hub", + "docker_keycloak", + "docker_casdoor", + "docker_clash", + ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 diff --git a/docker_openclaw/README.md b/docker_openclaw/README.md new file mode 100644 index 0000000..c870ea4 --- /dev/null +++ b/docker_openclaw/README.md @@ -0,0 +1,23 @@ +# OpenClaw + +`openclaw` image is built from `ghcr.io/openclaw/openclaw:latest` and adds: + +- source bootstrap and plugin setup +- default runtime config template +- startup script that auto-checks and installs `@larksuite/openclaw-lark` + +## Build Args + +- `OPENCLAW_GIT_URL`: source repo URL for build stage clone. +- `OPENCLAW_GIT_REF`: source branch or tag. +- `NPM_REGISTRY`: npm registry used by `pnpm`. + +## Runtime + +Default command: + +```bash +sh /usr/local/bin/bootstrap-openclaw.sh gateway --allow-unconfigured --bind lan --port 18789 +``` + +The startup script will create and normalize config under `/home/node/.openclaw/openclaw.json`. diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile new file mode 100644 index 0000000..1cc1066 --- /dev/null +++ b/docker_openclaw/openclaw.Dockerfile @@ -0,0 +1,41 @@ +FROM ghcr.io/openclaw/openclaw:latest + +ARG OPENCLAW_GIT_URL=https://gh-proxy.com/https://github.com/openclaw/openclaw.git +ARG OPENCLAW_GIT_REF=main +ARG NPM_REGISTRY=https://registry.npmmirror.com + +ENV DEBIAN_FRONTEND=noninteractive + +USER root + +WORKDIR /opt/openclaw + +RUN git clone --depth 1 --branch "${OPENCLAW_GIT_REF}" "${OPENCLAW_GIT_URL}" /opt/openclaw + +RUN corepack enable + +ENV NODE_ENV=development +ENV NPM_CONFIG_PRODUCTION=false + +RUN pnpm config set registry "${NPM_REGISTRY}" \ + && pnpm config set ignore-workspace-root-check true \ + && NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile \ + && pnpm build \ + && pnpm ui:install \ + && pnpm ui:build + +COPY work/openclaw.json /opt/openclaw/openclaw.template.json +COPY work/bootstrap-openclaw.sh /usr/local/bin/bootstrap-openclaw.sh + +RUN chmod +x /usr/local/bin/bootstrap-openclaw.sh \ + && mkdir -p /home/node/.openclaw \ + && chown -R node:node /opt/openclaw /home/node /usr/local/bin/bootstrap-openclaw.sh + +ENV HOME=/home/node +ENV XDG_CONFIG_HOME=/home/node/.openclaw +ENV OPENCLAW_CONFIG_TEMPLATE=/opt/openclaw/openclaw.template.json +ENV NODE_ENV=production + +USER node + +CMD ["sh", "/usr/local/bin/bootstrap-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] diff --git a/docker_openclaw/work/bootstrap-openclaw.sh b/docker_openclaw/work/bootstrap-openclaw.sh new file mode 100644 index 0000000..17de9fc --- /dev/null +++ b/docker_openclaw/work/bootstrap-openclaw.sh @@ -0,0 +1,109 @@ +#!/bin/sh +set -eu + +STATE_DIR="${OPENCLAW_STATE_DIR:-/home/node/.openclaw}" +CONFIG_PATH="${STATE_DIR}/openclaw.json" +TEMPLATE_PATH="${OPENCLAW_CONFIG_TEMPLATE:-/opt/openclaw/openclaw.template.json}" +PLUGIN_DIR="${STATE_DIR}/extensions/openclaw-lark" + +ensure_config_file() { + mkdir -p "${STATE_DIR}" + + if [ ! -s "${CONFIG_PATH}" ]; then + if [ -f "${TEMPLATE_PATH}" ]; then + cp "${TEMPLATE_PATH}" "${CONFIG_PATH}" + else + cat >"${CONFIG_PATH}" <<'JSON' +{ + "agents": { + "defaults": { + "workspace": "/home/node/.openclaw/workspace" + } + }, + "gateway": { + "controlUi": { + "dangerouslyAllowHostHeaderOriginFallback": true, + "dangerouslyDisableDeviceAuth": true + } + } +} +JSON + fi + fi +} + +remove_uninstalled_plugin_refs() { + node <<'NODE' +const fs = require('fs'); +const p = process.env.CONFIG_PATH; +const raw = fs.readFileSync(p, 'utf8'); +const c = JSON.parse(raw); + +if (!c.plugins) c.plugins = {}; +if (!c.plugins.entries) c.plugins.entries = {}; + +if (Array.isArray(c.plugins.allow)) { + c.plugins.allow = c.plugins.allow.filter((id) => id !== 'openclaw-lark'); +} +if (c.plugins.entries['openclaw-lark']) { + delete c.plugins.entries['openclaw-lark']; +} +if (!c.plugins.entries.feishu) { + c.plugins.entries.feishu = { enabled: false }; +} else { + c.plugins.entries.feishu.enabled = false; +} + +fs.writeFileSync(p, JSON.stringify(c, null, 2)); +NODE +} + +enable_feishu_plugin() { + node <<'NODE' +const fs = require('fs'); +const p = process.env.CONFIG_PATH; +const raw = fs.readFileSync(p, 'utf8'); +const c = JSON.parse(raw); + +if (!c.channels) c.channels = {}; +if (!c.channels.feishu) { + c.channels.feishu = { + enabled: true, + appId: '', + appSecret: '', + domain: 'feishu', + connectionMode: 'websocket', + requireMention: true, + dmPolicy: 'pairing', + groupPolicy: 'open', + allowFrom: [], + groupAllowFrom: [] + }; +} + +if (!c.plugins) c.plugins = {}; +if (!Array.isArray(c.plugins.allow)) c.plugins.allow = []; +if (!c.plugins.allow.includes('openclaw-lark')) c.plugins.allow.push('openclaw-lark'); +if (!c.plugins.entries) c.plugins.entries = {}; +c.plugins.entries.feishu = { enabled: false }; +c.plugins.entries['openclaw-lark'] = { enabled: true }; + +fs.writeFileSync(p, JSON.stringify(c, null, 2)); +NODE +} + +bootstrap() { + export CONFIG_PATH + ensure_config_file + + if [ ! -d "${PLUGIN_DIR}" ]; then + echo "[bootstrap] openclaw-lark not found, installing..." + remove_uninstalled_plugin_refs + node openclaw.mjs plugins install @larksuite/openclaw-lark + enable_feishu_plugin + echo "[bootstrap] openclaw-lark installation completed." + fi +} + +bootstrap +exec node openclaw.mjs "$@" \ No newline at end of file diff --git a/docker_openclaw/work/openclaw.json b/docker_openclaw/work/openclaw.json new file mode 100644 index 0000000..7f030a4 --- /dev/null +++ b/docker_openclaw/work/openclaw.json @@ -0,0 +1,40 @@ +{ + "agents": { + "defaults": { + "workspace": "/home/node/.openclaw/workspace" + } + }, + "channels": { + "feishu": { + "enabled": true, + "appId": "", + "appSecret": "", + "domain": "feishu", + "connectionMode": "websocket", + "requireMention": true, + "dmPolicy": "pairing", + "groupPolicy": "open", + "allowFrom": [], + "groupAllowFrom": [] + } + }, + "plugins": { + "allow": [ + "openclaw-lark" + ], + "entries": { + "feishu": { + "enabled": false + }, + "openclaw-lark": { + "enabled": true + } + } + }, + "gateway": { + "controlUi": { + "dangerouslyAllowHostHeaderOriginFallback": true, + "dangerouslyDisableDeviceAuth": true + } + } +} From 845be28665634c7574d9712f9c78b0a7bcf263b0 Mon Sep 17 00:00:00 2001 From: namelessman Date: Fri, 20 Mar 2026 22:54:15 +1300 Subject: [PATCH 02/14] refactor: update Dockerfile and bootstrap script for OpenClaw installation --- docker_openclaw/openclaw.Dockerfile | 30 +++++++++------------- docker_openclaw/work/bootstrap-openclaw.sh | 4 +-- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 1cc1066..7f3b9d3 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -1,8 +1,4 @@ -FROM ghcr.io/openclaw/openclaw:latest - -ARG OPENCLAW_GIT_URL=https://gh-proxy.com/https://github.com/openclaw/openclaw.git -ARG OPENCLAW_GIT_REF=main -ARG NPM_REGISTRY=https://registry.npmmirror.com +FROM node:22-bookworm-slim ENV DEBIAN_FRONTEND=noninteractive @@ -10,19 +6,17 @@ USER root WORKDIR /opt/openclaw -RUN git clone --depth 1 --branch "${OPENCLAW_GIT_REF}" "${OPENCLAW_GIT_URL}" /opt/openclaw - -RUN corepack enable - -ENV NODE_ENV=development -ENV NPM_CONFIG_PRODUCTION=false - -RUN pnpm config set registry "${NPM_REGISTRY}" \ - && pnpm config set ignore-workspace-root-check true \ - && NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile \ - && pnpm build \ - && pnpm ui:install \ - && pnpm ui:build +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + bash \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + python3 \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm COPY work/openclaw.json /opt/openclaw/openclaw.template.json COPY work/bootstrap-openclaw.sh /usr/local/bin/bootstrap-openclaw.sh diff --git a/docker_openclaw/work/bootstrap-openclaw.sh b/docker_openclaw/work/bootstrap-openclaw.sh index 17de9fc..e6014d0 100644 --- a/docker_openclaw/work/bootstrap-openclaw.sh +++ b/docker_openclaw/work/bootstrap-openclaw.sh @@ -99,11 +99,11 @@ bootstrap() { if [ ! -d "${PLUGIN_DIR}" ]; then echo "[bootstrap] openclaw-lark not found, installing..." remove_uninstalled_plugin_refs - node openclaw.mjs plugins install @larksuite/openclaw-lark + openclaw plugins install @larksuite/openclaw-lark enable_feishu_plugin echo "[bootstrap] openclaw-lark installation completed." fi } bootstrap -exec node openclaw.mjs "$@" \ No newline at end of file +exec openclaw "$@" \ No newline at end of file From 1356c2085f9292f8ab5e2e07ead4335f3b83acf0 Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 01:22:26 +0800 Subject: [PATCH 03/14] update openclaw install --- docker_openclaw/openclaw.Dockerfile | 43 ++++++------------- .../{openclaw.json => openclaw.template.json} | 2 +- ...ootstrap-openclaw.sh => start-openclaw.sh} | 31 ++++++------- 3 files changed, 31 insertions(+), 45 deletions(-) rename docker_openclaw/work/{openclaw.json => openclaw.template.json} (93%) rename docker_openclaw/work/{bootstrap-openclaw.sh => start-openclaw.sh} (77%) diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 7f3b9d3..ac1f388 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -1,35 +1,20 @@ -FROM node:22-bookworm-slim +# Distributed under the terms of the Modified BSD License. -ENV DEBIAN_FRONTEND=noninteractive +ARG BASE_NAMESPACE +ARG BASE_IMG="node" +FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} -USER root - -WORKDIR /opt/openclaw - -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - bash \ - build-essential \ - ca-certificates \ - cmake \ - curl \ - python3 \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm - -COPY work/openclaw.json /opt/openclaw/openclaw.template.json -COPY work/bootstrap-openclaw.sh /usr/local/bin/bootstrap-openclaw.sh - -RUN chmod +x /usr/local/bin/bootstrap-openclaw.sh \ - && mkdir -p /home/node/.openclaw \ - && chown -R node:node /opt/openclaw /home/node /usr/local/bin/bootstrap-openclaw.sh - -ENV HOME=/home/node +LABEL maintainer="postmaster@labnow.ai" ENV XDG_CONFIG_HOME=/home/node/.openclaw -ENV OPENCLAW_CONFIG_TEMPLATE=/opt/openclaw/openclaw.template.json ENV NODE_ENV=production -USER node +COPY work /opt/openclaw/ + +WORKDIR /opt/openclaw + +RUN set -eux && source /opt/utils/script-utils.sh \ + && curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ + && chmod +x /opt/openclaw/start-openclaw.sh \ + && ln -sf /opt/openclaw/start-openclaw.sh /usr/local/bin/ -CMD ["sh", "/usr/local/bin/bootstrap-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] +CMD ["sh", "start-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] diff --git a/docker_openclaw/work/openclaw.json b/docker_openclaw/work/openclaw.template.json similarity index 93% rename from docker_openclaw/work/openclaw.json rename to docker_openclaw/work/openclaw.template.json index 7f030a4..548574c 100644 --- a/docker_openclaw/work/openclaw.json +++ b/docker_openclaw/work/openclaw.template.json @@ -1,7 +1,7 @@ { "agents": { "defaults": { - "workspace": "/home/node/.openclaw/workspace" + "workspace": "/opt/openclaw/data/workspace" } }, "channels": { diff --git a/docker_openclaw/work/bootstrap-openclaw.sh b/docker_openclaw/work/start-openclaw.sh similarity index 77% rename from docker_openclaw/work/bootstrap-openclaw.sh rename to docker_openclaw/work/start-openclaw.sh index e6014d0..9aafa3b 100644 --- a/docker_openclaw/work/bootstrap-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,23 +1,24 @@ #!/bin/sh set -eu -STATE_DIR="${OPENCLAW_STATE_DIR:-/home/node/.openclaw}" -CONFIG_PATH="${STATE_DIR}/openclaw.json" -TEMPLATE_PATH="${OPENCLAW_CONFIG_TEMPLATE:-/opt/openclaw/openclaw.template.json}" -PLUGIN_DIR="${STATE_DIR}/extensions/openclaw-lark" +DIR_STATE="${OPENCLAW_DIR_STATE:-/opt/openclaw/data}" +DIR_PLUGIN="${DIR_STATE}/extensions/openclaw-lark" +PATH_CONFIG="${DIR_STATE}/openclaw.json" +PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-/opt/openclaw/openclaw.template.json}" + ensure_config_file() { - mkdir -p "${STATE_DIR}" + mkdir -p "${DIR_STATE}" - if [ ! -s "${CONFIG_PATH}" ]; then - if [ -f "${TEMPLATE_PATH}" ]; then - cp "${TEMPLATE_PATH}" "${CONFIG_PATH}" + if [ ! -s "${PATH_CONFIG}" ]; then + if [ -f "${PATH_TEMPLATE}" ]; then + cp "${PATH_TEMPLATE}" "${PATH_CONFIG}" else - cat >"${CONFIG_PATH}" <<'JSON' + cat >"${PATH_CONFIG}" <<'JSON' { "agents": { "defaults": { - "workspace": "/home/node/.openclaw/workspace" + "workspace": "/opt/openclaw/data/workspace" } }, "gateway": { @@ -35,7 +36,7 @@ JSON remove_uninstalled_plugin_refs() { node <<'NODE' const fs = require('fs'); -const p = process.env.CONFIG_PATH; +const p = process.env.PATH_CONFIG; const raw = fs.readFileSync(p, 'utf8'); const c = JSON.parse(raw); @@ -61,7 +62,7 @@ NODE enable_feishu_plugin() { node <<'NODE' const fs = require('fs'); -const p = process.env.CONFIG_PATH; +const p = process.env.PATH_CONFIG; const raw = fs.readFileSync(p, 'utf8'); const c = JSON.parse(raw); @@ -93,10 +94,10 @@ NODE } bootstrap() { - export CONFIG_PATH + export PATH_CONFIG ensure_config_file - if [ ! -d "${PLUGIN_DIR}" ]; then + if [ ! -d "${DIR_PLUGIN}" ]; then echo "[bootstrap] openclaw-lark not found, installing..." remove_uninstalled_plugin_refs openclaw plugins install @larksuite/openclaw-lark @@ -106,4 +107,4 @@ bootstrap() { } bootstrap -exec openclaw "$@" \ No newline at end of file +exec openclaw "$@" From a1f8f16b65d1fc4c10feae738fab9db7d19c9cee Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 16:36:37 +0800 Subject: [PATCH 04/14] debug openclaw install --- docker_openclaw/demo/docker-compose.yml | 53 +++++++++++++++++++++++++ docker_openclaw/openclaw.Dockerfile | 10 +++-- docker_openclaw/work/start-openclaw.sh | 9 +++-- 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 docker_openclaw/demo/docker-compose.yml diff --git a/docker_openclaw/demo/docker-compose.yml b/docker_openclaw/demo/docker-compose.yml new file mode 100644 index 0000000..c1b9f2f --- /dev/null +++ b/docker_openclaw/demo/docker-compose.yml @@ -0,0 +1,53 @@ +name: "svc-openclaw" + +services: + openclaw-gateway: + container_name: svc-openclaw-gateway + hostname: svc-openclaw-gateway + image: "quay.io/labnow0dev/openclaw:latest" + pull_policy: if_not_present + restart: unless-stopped + environment: + - TZ=Asia/Shanghai + - HOME=/home/node + - TERM=xterm-256color + - OPENCLAW_GATEWAY_BIND=${OPENCLAW_GATEWAY_BIND:-lan} + - OPENCLAW_GATEWAY_PORT=${OPENCLAW_GATEWAY_PORT:-18789} + - OPENCLAW_BRIDGE_PORT=${OPENCLAW_BRIDGE_PORT:-18790} + - XDG_CONFIG_HOME=/opt/openclaw/data + volumes: + - /data/openclaw:/opt/openclaw/data + ports: + - "${OPENCLAW_GATEWAY_PORT:-18789}:18789" + - "${OPENCLAW_BRIDGE_PORT:-18790}:18790" + init: true + command: + [ + "sh", + "/usr/local/bin/start-openclaw.sh", + "gateway", + "--allow-unconfigured", + "--bind", + "${OPENCLAW_GATEWAY_BIND:-lan}", + "--port", + "${OPENCLAW_GATEWAY_PORT:-18789}" + ] + + openclaw-cli: + container_name: svc-openclaw-cli + hostname: svc-openclaw-cli + image: "quay.io/labnow0dev/openclaw:latest" + pull_policy: if_not_present + restart: "no" + environment: + - TZ=Asia/Shanghai + - HOME=/home/node + - TERM=xterm-256color + - BROWSER=echo + - XDG_CONFIG_HOME=/opt/openclaw/data + volumes: + - /data/openclaw:/opt/openclaw/data + stdin_open: true + tty: true + init: true + entrypoint: ["node", "openclaw.mjs"] diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index ac1f388..7a372c5 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -5,15 +5,19 @@ ARG BASE_IMG="node" FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} LABEL maintainer="postmaster@labnow.ai" -ENV XDG_CONFIG_HOME=/home/node/.openclaw ENV NODE_ENV=production +ENV HOME=/opt/openclaw/ +ENV XDG_CONFIG_HOME=/opt/openclaw/data/.config COPY work /opt/openclaw/ WORKDIR /opt/openclaw -RUN set -eux && source /opt/utils/script-utils.sh \ - && curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ +RUN set -eux && source /opt/utils/script-setup.sh \ + ## curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ + && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ + && setup_node_pnpm \ + && pnpm --no-fund --no-audit install -g openclaw@next \ && chmod +x /opt/openclaw/start-openclaw.sh \ && ln -sf /opt/openclaw/start-openclaw.sh /usr/local/bin/ diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index 9aafa3b..b6b1a5c 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,14 +1,15 @@ #!/bin/sh set -eu -DIR_STATE="${OPENCLAW_DIR_STATE:-/opt/openclaw/data}" -DIR_PLUGIN="${DIR_STATE}/extensions/openclaw-lark" +HOME_CLAW=${HOME:-/opt/openclaw} +DIR_STATE="${OPENCLAW_DIR_STATE:-$HOME_CLAW/data}" +DIR_PLUGIN="${HOME_CLAW}/extensions/" PATH_CONFIG="${DIR_STATE}/openclaw.json" -PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-/opt/openclaw/openclaw.template.json}" +PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$HOME_CLAW/openclaw.template.json}" ensure_config_file() { - mkdir -p "${DIR_STATE}" + mkdir -pv "${DIR_STATE}" if [ ! -s "${PATH_CONFIG}" ]; then if [ -f "${PATH_TEMPLATE}" ]; then From d0724ebefb83bee13df109bd901ac33d20750261 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Tue, 31 Mar 2026 10:18:38 +0000 Subject: [PATCH 05/14] optimize claw build --- docker_openclaw/openclaw.Dockerfile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 7a372c5..7e80c8b 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -6,6 +6,8 @@ FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} LABEL maintainer="postmaster@labnow.ai" ENV NODE_ENV=production +ENV PNPM_HOME=/opt/node/pnpm +ENV PNPM_STORE_DIR=/opt/node/pnpm-store ENV HOME=/opt/openclaw/ ENV XDG_CONFIG_HOME=/opt/openclaw/data/.config @@ -14,11 +16,12 @@ COPY work /opt/openclaw/ WORKDIR /opt/openclaw RUN set -eux && source /opt/utils/script-setup.sh \ + && chmod +x /opt/openclaw/start-openclaw.sh && ln -sf /opt/openclaw/start-openclaw.sh /usr/local/bin/ \ ## curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ - && setup_node_pnpm \ - && pnpm --no-fund --no-audit install -g openclaw@next \ - && chmod +x /opt/openclaw/start-openclaw.sh \ - && ln -sf /opt/openclaw/start-openclaw.sh /usr/local/bin/ + && setup_node_pnpm 10 && source /etc/profile.d/path-pnpm.sh \ + && pnpm install -g openclaw@latest \ + && openclaw --version + CMD ["sh", "start-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] From dd1fbd3b5a3cfd98728ab053795fc6fac544bf14 Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 19:34:54 +0800 Subject: [PATCH 06/14] opt openclaw build --- docker_devbox/dev.Dockerfile | 14 +- docker_devbox/hub.Dockerfile | 8 +- docker_nocobase/nocobase.Dockerfile | 2 +- docker_openclaw/openclaw.Dockerfile | 5 +- .../work/openclaw-plugin-installer.js | 184 ++++++++++++++++++ docker_openclaw/work/start-openclaw.sh | 78 +------- docker_searxng/searxng.Dockerfile | 6 +- docker_storebox/storebox.Dockerfile | 10 +- 8 files changed, 217 insertions(+), 90 deletions(-) create mode 100644 docker_openclaw/work/openclaw-plugin-installer.js diff --git a/docker_devbox/dev.Dockerfile b/docker_devbox/dev.Dockerfile index e452d9f..dc22b18 100644 --- a/docker_devbox/dev.Dockerfile +++ b/docker_devbox/dev.Dockerfile @@ -21,30 +21,30 @@ COPY work /opt/utils/ RUN set -eux && source /opt/utils/script-utils.sh \ && chmod +x /opt/utils/*.sh \ - # ----------------------------- Setup Jupyter: Basic Configurations and Extensions + ## ----------------------------- Setup Jupyter: Basic Configurations and Extensions && mkdir -pv /opt/conda/etc/jupyter/ \ && mv /opt/utils/etc_jupyter/* /opt/conda/etc/jupyter/ && rm -rf /opt/utils/etc_jupyter \ && mv /opt/utils/start-*.sh /usr/local/bin/ && chmod +x /usr/local/bin/start-*.sh \ && ln -sf /usr/local/bin/start-jupyterlab.sh /usr/local/bin/start-notebook.sh \ && source /opt/utils/script-devbox-jupyter.sh \ && for profile in $(echo $ARG_PROFILE_JUPYTER | tr "," "\n") ; do ( setup_jupyter_${profile} || true ) ; done \ - # ----------------------------- If installing coder-server # https://github.com/cdr/code-server/releases + ## ----------------------------- If installing coder-server # https://github.com/cdr/code-server/releases && source /opt/utils/script-devbox-vscode.sh \ && for profile in $(echo $ARG_PROFILE_VSCODE | tr "," "\n") ; do ( setup_vscode_${profile} || true ) ; done \ - # ----------------------------- If not keeping NodeJS, remove NoedJS to reduce image size + ## ----------------------------- If not keeping NodeJS, remove NoedJS to reduce image size && if [ ${ARG_KEEP_NODEJS} = "false" ] ; then \ echo "Removing Node/NPM..." && rm -rf /usr/bin/node /usr/bin/npm /usr/bin/npx /opt/node ; \ else \ echo "Keep NodeJS as ARG_KEEP_NODEJS defiend as: ${ARG_KEEP_NODEJS}" ; \ fi \ - # ----------------------------- If installing R IDEs: R_rstudio and R_rshiny + ## ----------------------------- If installing R IDEs: R_rstudio and R_rshiny && source /opt/utils/script-devbox-rstudio.sh \ && for profile in $(echo $ARG_PROFILE_R | tr "," "\n") ; do ( setup_R_${profile} ) ; done \ - # ----------------------------- Install supervisord + ## ----------------------------- Install supervisord && source /opt/utils/script-setup-sys.sh && setup_supervisord \ - # ----------------------------- Install caddy + ## ----------------------------- Install caddy && source /opt/utils/script-setup-net.sh && setup_caddy \ - # Clean up and display components version information... + ## Clean up and display components version information... && list_installed_packages && install__clean ENTRYPOINT ["tini", "-g", "--"] diff --git a/docker_devbox/hub.Dockerfile b/docker_devbox/hub.Dockerfile index 0319e9a..24df020 100644 --- a/docker_devbox/hub.Dockerfile +++ b/docker_devbox/hub.Dockerfile @@ -15,23 +15,23 @@ COPY work /opt/utils/ RUN set -eux \ && chmod +x /opt/utils/*.sh && rm -rf /opt/utils/etc_jupyter \ - # Setup JupyterHub + ## Setup JupyterHub && source /opt/utils/script-devbox-jupyter.sh \ && for profile in $(echo $ARG_PROFILE_JUPYTER | tr "," "\n") ; do ( setup_jupyter_${profile} ) ; done \ - # If not keeping NodeJS, remove NoedJS to reduce image size, and install Traefik instead + ## If not keeping NodeJS, remove NoedJS to reduce image size, and install Traefik instead && if [ ${ARG_KEEP_NODEJS} = "false" ] ; then \ echo "Removing Node/NPM..." && rm -rf /usr/bin/node /usr/bin/npm /usr/bin/npx /opt/node ; \ echo "Installing Traefik to server as proxy:" && source /opt/utils/script-setup-net.sh && setup_traefik ; \ else \ echo "Keep NodeJS as ARG_KEEP_NODEJS defiend as: ${ARG_KEEP_NODEJS}" ; \ fi \ - # network-tools https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/main/images/network-tools/Dockerfile + ## network-tools https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/main/images/network-tools/Dockerfile && apt-get update && apt-get install -y --no-install-recommends \ iptables dnsutils libcurl4 libpq5 sqlite3 \ && curl -fsSL -o /usr/local/bin/start-configurable-http-proxy.sh https://raw.githubusercontent.com/jupyterhub/configurable-http-proxy/refs/heads/main/chp-docker-entrypoint \ && mv /opt/utils/start-*.sh /usr/local/bin/ \ && chmod +x /usr/local/bin/start-*.sh \ - # Clean up and display components version information... + ## Clean up and display components version information... && source /opt/utils/script-utils.sh && install__clean && list_installed_packages ENTRYPOINT ["tini", "-g", "--"] diff --git a/docker_nocobase/nocobase.Dockerfile b/docker_nocobase/nocobase.Dockerfile index 79ecf71..c615c31 100644 --- a/docker_nocobase/nocobase.Dockerfile +++ b/docker_nocobase/nocobase.Dockerfile @@ -25,7 +25,7 @@ RUN set -eux \ && mv /opt/utils/docker-entrypoint.sh /opt/nocobase/ \ && chmod +x /opt/nocobase/*.sh \ && ls -alh \ - # Clean up and display components version information... + ## Clean up and display components version information... && find ./node_modules -type f \( -name "README.md" -o -name "License" \) -delete 2>/dev/null \ && find ./node_modules -type d \( -name "test" -o -name "tests" -o -name "__tests__" -o -name "docs" -o -name "doc" \) -exec rm -rf {} + 2>/dev/null \ && list_installed_packages && install__clean diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 7e80c8b..3585b5c 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -21,7 +21,10 @@ RUN set -eux && source /opt/utils/script-setup.sh \ && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ && setup_node_pnpm 10 && source /etc/profile.d/path-pnpm.sh \ && pnpm install -g openclaw@latest \ - && openclaw --version + && openclaw --version \ + ## Clean up and display components version information... + && list_installed_packages && install__clean +EXPOSE 18798 CMD ["sh", "start-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] diff --git a/docker_openclaw/work/openclaw-plugin-installer.js b/docker_openclaw/work/openclaw-plugin-installer.js new file mode 100644 index 0000000..55a4b5e --- /dev/null +++ b/docker_openclaw/work/openclaw-plugin-installer.js @@ -0,0 +1,184 @@ +#!/usr/bin/env node +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { execFileSync } = require('child_process'); + +function parseArgs(argv) { + const args = { _: [] }; + for (let i = 0; i < argv.length; i += 1) { + const token = argv[i]; + if (!token.startsWith('--')) { + args._.push(token); + continue; + } + + const eqIndex = token.indexOf('='); + if (eqIndex > -1) { + const key = token.slice(2, eqIndex); + const value = token.slice(eqIndex + 1); + args[key] = value; + continue; + } + + const key = token.slice(2); + const next = argv[i + 1]; + if (!next || next.startsWith('--')) { + args[key] = true; + continue; + } + + args[key] = next; + i += 1; + } + + return args; +} + +function ensureConfigShape(config) { + if (!config.plugins) config.plugins = {}; + if (!Array.isArray(config.plugins.allow)) config.plugins.allow = []; + if (!config.plugins.entries) config.plugins.entries = {}; +} + +function loadConfig(configPath) { + const raw = fs.readFileSync(configPath, 'utf8'); + return JSON.parse(raw); +} + +function saveConfig(configPath, config) { + fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); +} + +function pluginDirectoryExists(homeClaw, pluginDirName) { + const pluginPath = path.join(homeClaw, 'extensions', pluginDirName); + return fs.existsSync(pluginPath); +} + +function installOpenclawPlugin(packageName) { + execFileSync('openclaw', ['plugins', 'install', packageName], { + stdio: 'inherit' + }); +} + +function cleanupFeishuLarkRefs(config) { + ensureConfigShape(config); + config.plugins.allow = config.plugins.allow.filter((id) => id !== 'openclaw-lark'); + delete config.plugins.entries['openclaw-lark']; + + if (!config.plugins.entries.feishu) { + config.plugins.entries.feishu = { enabled: false }; + } else { + config.plugins.entries.feishu.enabled = false; + } +} + +function applyFeishuLarkConfig(config) { + if (!config.channels) config.channels = {}; + if (!config.channels.feishu) { + config.channels.feishu = { + enabled: true, + appId: '', + appSecret: '', + domain: 'feishu', + connectionMode: 'websocket', + requireMention: true, + dmPolicy: 'pairing', + groupPolicy: 'open', + allowFrom: [], + groupAllowFrom: [] + }; + } + + ensureConfigShape(config); + + if (!config.plugins.allow.includes('openclaw-lark')) { + config.plugins.allow.push('openclaw-lark'); + } + + config.plugins.entries.feishu = { enabled: false }; + config.plugins.entries['openclaw-lark'] = { enabled: true }; +} + +function installPlugin(options) { + const { + homeClaw, + configPath, + packageName, + pluginId, + pluginDirName, + forceInstall, + profile + } = options; + + const exists = pluginDirectoryExists(homeClaw, pluginDirName); + if (exists && !forceInstall) { + console.log(`[plugin-installer] ${pluginId} already exists at extensions/${pluginDirName}, skip install.`); + return; + } + + const config = loadConfig(configPath); + + if (profile === 'feishu-lark') { + cleanupFeishuLarkRefs(config); + saveConfig(configPath, config); + } + + console.log(`[plugin-installer] Installing ${packageName} ...`); + installOpenclawPlugin(packageName); + + const updated = loadConfig(configPath); + if (profile === 'feishu-lark') { + applyFeishuLarkConfig(updated); + } + saveConfig(configPath, updated); + + console.log(`[plugin-installer] ${packageName} installed successfully.`); +} + +function printHelp() { + console.log(`Usage:\n node openclaw-plugin-installer.js install --package --id [options]\n\nOptions:\n --home OpenClaw home path (default: /opt/openclaw)\n --config OpenClaw config path (required)\n --plugin-dir Plugin directory name under extensions/ (default: --id value)\n --profile Optional profile (supported: feishu-lark)\n --force Force install even if plugin dir exists\n`); +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + const command = args._[0]; + + if (!command || command === 'help' || command === '--help') { + printHelp(); + process.exit(command ? 0 : 1); + } + + if (command !== 'install') { + console.error(`[plugin-installer] Unsupported command: ${command}`); + printHelp(); + process.exit(1); + } + + const packageName = args.package; + const pluginId = args.id; + const configPath = args.config; + const homeClaw = args.home || '/opt/openclaw'; + const pluginDirName = args['plugin-dir'] || pluginId; + const forceInstall = Boolean(args.force); + const profile = args.profile || ''; + + if (!packageName || !pluginId || !configPath) { + console.error('[plugin-installer] Missing required args: --package, --id, --config'); + printHelp(); + process.exit(1); + } + + installPlugin({ + homeClaw, + configPath, + packageName, + pluginId, + pluginDirName, + forceInstall, + profile + }); +} + +main(); diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index b6b1a5c..e6eb127 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -3,9 +3,9 @@ set -eu HOME_CLAW=${HOME:-/opt/openclaw} DIR_STATE="${OPENCLAW_DIR_STATE:-$HOME_CLAW/data}" -DIR_PLUGIN="${HOME_CLAW}/extensions/" PATH_CONFIG="${DIR_STATE}/openclaw.json" PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$HOME_CLAW/openclaw.template.json}" +PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$HOME_CLAW/openclaw-plugin-installer.js}" ensure_config_file() { @@ -34,77 +34,17 @@ JSON fi } -remove_uninstalled_plugin_refs() { - node <<'NODE' -const fs = require('fs'); -const p = process.env.PATH_CONFIG; -const raw = fs.readFileSync(p, 'utf8'); -const c = JSON.parse(raw); - -if (!c.plugins) c.plugins = {}; -if (!c.plugins.entries) c.plugins.entries = {}; - -if (Array.isArray(c.plugins.allow)) { - c.plugins.allow = c.plugins.allow.filter((id) => id !== 'openclaw-lark'); -} -if (c.plugins.entries['openclaw-lark']) { - delete c.plugins.entries['openclaw-lark']; -} -if (!c.plugins.entries.feishu) { - c.plugins.entries.feishu = { enabled: false }; -} else { - c.plugins.entries.feishu.enabled = false; -} - -fs.writeFileSync(p, JSON.stringify(c, null, 2)); -NODE -} - -enable_feishu_plugin() { - node <<'NODE' -const fs = require('fs'); -const p = process.env.PATH_CONFIG; -const raw = fs.readFileSync(p, 'utf8'); -const c = JSON.parse(raw); - -if (!c.channels) c.channels = {}; -if (!c.channels.feishu) { - c.channels.feishu = { - enabled: true, - appId: '', - appSecret: '', - domain: 'feishu', - connectionMode: 'websocket', - requireMention: true, - dmPolicy: 'pairing', - groupPolicy: 'open', - allowFrom: [], - groupAllowFrom: [] - }; -} - -if (!c.plugins) c.plugins = {}; -if (!Array.isArray(c.plugins.allow)) c.plugins.allow = []; -if (!c.plugins.allow.includes('openclaw-lark')) c.plugins.allow.push('openclaw-lark'); -if (!c.plugins.entries) c.plugins.entries = {}; -c.plugins.entries.feishu = { enabled: false }; -c.plugins.entries['openclaw-lark'] = { enabled: true }; - -fs.writeFileSync(p, JSON.stringify(c, null, 2)); -NODE -} - bootstrap() { export PATH_CONFIG - ensure_config_file + openclaw config set skills.install.nodeManager pnpm - if [ ! -d "${DIR_PLUGIN}" ]; then - echo "[bootstrap] openclaw-lark not found, installing..." - remove_uninstalled_plugin_refs - openclaw plugins install @larksuite/openclaw-lark - enable_feishu_plugin - echo "[bootstrap] openclaw-lark installation completed." - fi + ensure_config_file + node "${PATH_PLUGIN_INSTALLER}" install \ + --home "${HOME_CLAW}" \ + --config "${PATH_CONFIG}" \ + --package "@larksuite/openclaw-lark" \ + --id "openclaw-lark" \ + --profile "feishu-lark" } bootstrap diff --git a/docker_searxng/searxng.Dockerfile b/docker_searxng/searxng.Dockerfile index 8339bc6..ec6c807 100644 --- a/docker_searxng/searxng.Dockerfile +++ b/docker_searxng/searxng.Dockerfile @@ -25,11 +25,11 @@ RUN set -eux \ && pip install --use-pep517 --no-build-isolation -e . \ && mv /tmp/searxng/* /opt/searxng && ln -sf /opt/searxng/etc /etc/searxng \ && ln -sf /opt/searxng /usr/local/ \ - # ----------------------------- Install supervisord + ## ----------------------------- Install supervisord && source /opt/utils/script-setup-sys.sh && setup_supervisord \ - # ----------------------------- Install caddy + ## ----------------------------- Install caddy && source /opt/utils/script-setup-net.sh && setup_caddy \ - # Clean up and display components version information... + ## Clean up and display components version information... && fix_permission searxng /opt/searxng/ \ && chmod +x /opt/searxng/*.sh \ && chmod -R ugo+rws /var/log /var/run \ diff --git a/docker_storebox/storebox.Dockerfile b/docker_storebox/storebox.Dockerfile index 581a992..50db42b 100644 --- a/docker_storebox/storebox.Dockerfile +++ b/docker_storebox/storebox.Dockerfile @@ -8,13 +8,13 @@ FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} COPY work /opt/utils RUN set -eux \ - # ----------------------------- Install supervisord + ## ----------------------------- Install supervisord && source /opt/utils/script-setup-sys.sh && setup_supervisord \ - # ----------------------------- Install caddy + ## ----------------------------- Install caddy && source /opt/utils/script-setup-net.sh && setup_caddy \ - # ----------------------------- Install alist + ## ----------------------------- Install alist && source /opt/utils/script-setup-alist.sh && setup_alist \ - # ----------------------------- Install rclone + ## ----------------------------- Install rclone && source /opt/utils/script-setup-rclone.sh && setup_rclone \ - # Clean up and display components version information... + ## Clean up and display components version information... && list_installed_packages && install__clean From 5a315c476b6180176543441d9173f66c937282e6 Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 20:11:54 +0800 Subject: [PATCH 07/14] update claw --- docker_openclaw/openclaw.Dockerfile | 6 +- .../work/openclaw-plugin-installer.js | 147 +++++++++++------- docker_openclaw/work/start-openclaw.sh | 11 +- 3 files changed, 104 insertions(+), 60 deletions(-) diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 3585b5c..4f6a1e5 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -9,7 +9,9 @@ ENV NODE_ENV=production ENV PNPM_HOME=/opt/node/pnpm ENV PNPM_STORE_DIR=/opt/node/pnpm-store ENV HOME=/opt/openclaw/ -ENV XDG_CONFIG_HOME=/opt/openclaw/data/.config +ENV XDG_CONFIG_HOME=/opt/openclaw/data + +ENV OPENCLAW_HIDE_BANNER=1 COPY work /opt/openclaw/ @@ -25,6 +27,6 @@ RUN set -eux && source /opt/utils/script-setup.sh \ ## Clean up and display components version information... && list_installed_packages && install__clean - +VOLUME ["/opt/openclaw/data"] EXPOSE 18798 CMD ["sh", "start-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] diff --git a/docker_openclaw/work/openclaw-plugin-installer.js b/docker_openclaw/work/openclaw-plugin-installer.js index 55a4b5e..088bcb8 100644 --- a/docker_openclaw/work/openclaw-plugin-installer.js +++ b/docker_openclaw/work/openclaw-plugin-installer.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path'); -const { execFileSync } = require('child_process'); +const { spawnSync } = require('child_process'); function parseArgs(argv) { const args = { _: [] }; @@ -36,6 +36,14 @@ function parseArgs(argv) { return args; } +function parseCommaList(value) { + if (!value || value === true) return []; + return String(value) + .split(',') + .map((item) => item.trim()) + .filter(Boolean); +} + function ensureConfigShape(config) { if (!config.plugins) config.plugins = {}; if (!Array.isArray(config.plugins.allow)) config.plugins.allow = []; @@ -51,54 +59,66 @@ function saveConfig(configPath, config) { fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); } -function pluginDirectoryExists(homeClaw, pluginDirName) { - const pluginPath = path.join(homeClaw, 'extensions', pluginDirName); - return fs.existsSync(pluginPath); +function pluginDirectoryCandidates(homeClaw, pluginDirName, extensionsDir) { + const roots = new Set(); + const normalizedHome = homeClaw && path.resolve(homeClaw); + + if (extensionsDir) roots.add(path.resolve(extensionsDir)); + if (normalizedHome) { + roots.add(path.join(normalizedHome, 'extensions')); + roots.add(path.join(normalizedHome, '.openclaw', 'extensions')); + } + roots.add(path.join('/opt/openclaw', '.openclaw', 'extensions')); + + return Array.from(roots).map((root) => path.join(root, pluginDirName)); } -function installOpenclawPlugin(packageName) { - execFileSync('openclaw', ['plugins', 'install', packageName], { - stdio: 'inherit' - }); +function pluginDirectoryExists(homeClaw, pluginDirName, extensionsDir) { + const candidates = pluginDirectoryCandidates(homeClaw, pluginDirName, extensionsDir); + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return { exists: true, path: candidate }; + } + } + return { exists: false, path: '' }; } -function cleanupFeishuLarkRefs(config) { - ensureConfigShape(config); - config.plugins.allow = config.plugins.allow.filter((id) => id !== 'openclaw-lark'); - delete config.plugins.entries['openclaw-lark']; +function installOpenclawPlugin(packageName, env) { + const result = spawnSync('openclaw', ['plugins', 'install', packageName], { + encoding: 'utf8', + env + }); - if (!config.plugins.entries.feishu) { - config.plugins.entries.feishu = { enabled: false }; - } else { - config.plugins.entries.feishu.enabled = false; + if (result.stdout) process.stdout.write(result.stdout); + if (result.stderr) process.stderr.write(result.stderr); + + if (result.status === 0) { + return { installed: true, alreadyExists: false }; } -} -function applyFeishuLarkConfig(config) { - if (!config.channels) config.channels = {}; - if (!config.channels.feishu) { - config.channels.feishu = { - enabled: true, - appId: '', - appSecret: '', - domain: 'feishu', - connectionMode: 'websocket', - requireMention: true, - dmPolicy: 'pairing', - groupPolicy: 'open', - allowFrom: [], - groupAllowFrom: [] - }; + const output = `${result.stdout || ''}\n${result.stderr || ''}`; + if (output.includes('plugin already exists')) { + return { installed: false, alreadyExists: true }; } + const err = new Error(`Command failed: openclaw plugins install ${packageName}`); + err.status = result.status; + err.output = output; + throw err; +} + +function applyPluginConfig(config, pluginId, disableEntries) { ensureConfigShape(config); - if (!config.plugins.allow.includes('openclaw-lark')) { - config.plugins.allow.push('openclaw-lark'); + if (!config.plugins.allow.includes(pluginId)) { + config.plugins.allow.push(pluginId); } - config.plugins.entries.feishu = { enabled: false }; - config.plugins.entries['openclaw-lark'] = { enabled: true }; + config.plugins.entries[pluginId] = { enabled: true }; + + for (const entryId of disableEntries) { + config.plugins.entries[entryId] = { enabled: false }; + } } function installPlugin(options) { @@ -109,36 +129,49 @@ function installPlugin(options) { pluginId, pluginDirName, forceInstall, - profile + stateDir, + extensionsDir, + disableEntries } = options; - const exists = pluginDirectoryExists(homeClaw, pluginDirName); - if (exists && !forceInstall) { - console.log(`[plugin-installer] ${pluginId} already exists at extensions/${pluginDirName}, skip install.`); - return; - } + fs.mkdirSync(stateDir, { recursive: true }); + fs.mkdirSync(extensionsDir, { recursive: true }); - const config = loadConfig(configPath); + const installEnv = { + ...process.env, + OPENCLAW_DIR_STATE: stateDir, + OPENCLAW_DIR_EXTENSIONS: extensionsDir, + XDG_CONFIG_HOME: stateDir + }; - if (profile === 'feishu-lark') { - cleanupFeishuLarkRefs(config); - saveConfig(configPath, config); - } + const existence = pluginDirectoryExists(homeClaw, pluginDirName, extensionsDir); + const needInstall = forceInstall || !existence.exists; - console.log(`[plugin-installer] Installing ${packageName} ...`); - installOpenclawPlugin(packageName); + if (!needInstall) { + console.log(`[plugin-installer] ${pluginId} already exists at ${existence.path}, skip install.`); + } else { + console.log(`[plugin-installer] Installing ${packageName} ...`); + const installResult = installOpenclawPlugin(packageName, installEnv); + if (installResult.alreadyExists) { + console.log(`[plugin-installer] ${pluginId} already exists, treat as idempotent success.`); + } + } const updated = loadConfig(configPath); - if (profile === 'feishu-lark') { - applyFeishuLarkConfig(updated); - } + applyPluginConfig(updated, pluginId, disableEntries); saveConfig(configPath, updated); + const targetPluginPath = path.join(extensionsDir, pluginDirName); + if (!fs.existsSync(targetPluginPath)) { + const actual = pluginDirectoryExists(homeClaw, pluginDirName, extensionsDir); + console.warn(`[plugin-installer] WARN: expected plugin under ${targetPluginPath}, actual: ${actual.path || 'not found'}`); + } + console.log(`[plugin-installer] ${packageName} installed successfully.`); } function printHelp() { - console.log(`Usage:\n node openclaw-plugin-installer.js install --package --id [options]\n\nOptions:\n --home OpenClaw home path (default: /opt/openclaw)\n --config OpenClaw config path (required)\n --plugin-dir Plugin directory name under extensions/ (default: --id value)\n --profile Optional profile (supported: feishu-lark)\n --force Force install even if plugin dir exists\n`); + console.log(`Usage:\n node openclaw-plugin-installer.js install --package --id [options]\n\nOptions:\n --home OpenClaw home path (default: /opt/openclaw)\n --config OpenClaw config path (required)\n --state-dir OpenClaw state dir (default: dirname(--config))\n --extensions-dir Plugin install dir (default: /extensions)\n --plugin-dir Plugin directory name (default: --id value)\n --disable-entry Comma separated plugin entry ids to disable\n --force Force install even if plugin dir exists\n`); } function main() { @@ -160,9 +193,11 @@ function main() { const pluginId = args.id; const configPath = args.config; const homeClaw = args.home || '/opt/openclaw'; + const stateDir = path.resolve(args['state-dir'] || path.dirname(configPath || '')); + const extensionsDir = path.resolve(args['extensions-dir'] || path.join(stateDir, 'extensions')); const pluginDirName = args['plugin-dir'] || pluginId; const forceInstall = Boolean(args.force); - const profile = args.profile || ''; + const disableEntries = parseCommaList(args['disable-entry']); if (!packageName || !pluginId || !configPath) { console.error('[plugin-installer] Missing required args: --package, --id, --config'); @@ -177,7 +212,9 @@ function main() { pluginId, pluginDirName, forceInstall, - profile + stateDir, + extensionsDir, + disableEntries }); } diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index e6eb127..d8b621f 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,8 +1,8 @@ #!/bin/sh set -eu -HOME_CLAW=${HOME:-/opt/openclaw} -DIR_STATE="${OPENCLAW_DIR_STATE:-$HOME_CLAW/data}" +HOME_CLAW="${HOME:-$HOME_CLAW}" +DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$HOME_CLAW/data}}" PATH_CONFIG="${DIR_STATE}/openclaw.json" PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$HOME_CLAW/openclaw.template.json}" PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$HOME_CLAW/openclaw-plugin-installer.js}" @@ -36,15 +36,20 @@ JSON bootstrap() { export PATH_CONFIG + export OPENCLAW_DIR_STATE="${DIR_STATE}" + export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" + export XDG_CONFIG_HOME="${OPENCLAW_DIR_STATE}" openclaw config set skills.install.nodeManager pnpm ensure_config_file node "${PATH_PLUGIN_INSTALLER}" install \ --home "${HOME_CLAW}" \ --config "${PATH_CONFIG}" \ + --state-dir "${OPENCLAW_DIR_STATE}" \ + --extensions-dir "${OPENCLAW_DIR_EXTENSIONS}" \ --package "@larksuite/openclaw-lark" \ --id "openclaw-lark" \ - --profile "feishu-lark" + --disable-entry "feishu" } bootstrap From f59e7751cf0c7454549d6add4821f9ac2403f5b4 Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 20:43:58 +0800 Subject: [PATCH 08/14] debug build --- docker_openclaw/demo/docker-compose.yml | 20 -- docker_openclaw/openclaw.Dockerfile | 18 +- .../work/openclaw-plugin-installer.js | 292 +++++++++++++----- docker_openclaw/work/start-openclaw.sh | 53 ++-- 4 files changed, 238 insertions(+), 145 deletions(-) diff --git a/docker_openclaw/demo/docker-compose.yml b/docker_openclaw/demo/docker-compose.yml index c1b9f2f..184438b 100644 --- a/docker_openclaw/demo/docker-compose.yml +++ b/docker_openclaw/demo/docker-compose.yml @@ -9,29 +9,12 @@ services: restart: unless-stopped environment: - TZ=Asia/Shanghai - - HOME=/home/node - - TERM=xterm-256color - - OPENCLAW_GATEWAY_BIND=${OPENCLAW_GATEWAY_BIND:-lan} - - OPENCLAW_GATEWAY_PORT=${OPENCLAW_GATEWAY_PORT:-18789} - - OPENCLAW_BRIDGE_PORT=${OPENCLAW_BRIDGE_PORT:-18790} - - XDG_CONFIG_HOME=/opt/openclaw/data volumes: - /data/openclaw:/opt/openclaw/data ports: - "${OPENCLAW_GATEWAY_PORT:-18789}:18789" - "${OPENCLAW_BRIDGE_PORT:-18790}:18790" init: true - command: - [ - "sh", - "/usr/local/bin/start-openclaw.sh", - "gateway", - "--allow-unconfigured", - "--bind", - "${OPENCLAW_GATEWAY_BIND:-lan}", - "--port", - "${OPENCLAW_GATEWAY_PORT:-18789}" - ] openclaw-cli: container_name: svc-openclaw-cli @@ -41,10 +24,7 @@ services: restart: "no" environment: - TZ=Asia/Shanghai - - HOME=/home/node - - TERM=xterm-256color - BROWSER=echo - - XDG_CONFIG_HOME=/opt/openclaw/data volumes: - /data/openclaw:/opt/openclaw/data stdin_open: true diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 4f6a1e5..6da2db3 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -8,25 +8,27 @@ LABEL maintainer="postmaster@labnow.ai" ENV NODE_ENV=production ENV PNPM_HOME=/opt/node/pnpm ENV PNPM_STORE_DIR=/opt/node/pnpm-store -ENV HOME=/opt/openclaw/ -ENV XDG_CONFIG_HOME=/opt/openclaw/data - -ENV OPENCLAW_HIDE_BANNER=1 COPY work /opt/openclaw/ -WORKDIR /opt/openclaw - RUN set -eux && source /opt/utils/script-setup.sh \ && chmod +x /opt/openclaw/start-openclaw.sh && ln -sf /opt/openclaw/start-openclaw.sh /usr/local/bin/ \ ## curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ && setup_node_pnpm 10 && source /etc/profile.d/path-pnpm.sh \ + && export PNPM_HOME=/opt/node/pnpm \ + && export PNPM_STORE_DIR=/opt/node/pnpm-store \ + && export PATH="${PNPM_HOME}:${PATH}" \ + && mkdir -p ${PNPM_HOME} ${PNPM_STORE_DIR} \ && pnpm install -g openclaw@latest \ && openclaw --version \ ## Clean up and display components version information... && list_installed_packages && install__clean +ENV HOME=/opt/openclaw/ +ENV XDG_CONFIG_HOME=/opt/openclaw/data +ENV OPENCLAW_HIDE_BANNER=1 +WORKDIR /opt/openclaw VOLUME ["/opt/openclaw/data"] -EXPOSE 18798 -CMD ["sh", "start-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "lan", "--port", "18789"] +EXPOSE 18789 18790 +CMD ["sh", "start-openclaw.sh", "gateway", "--allow-unconfigured", "--bind", "${OPENCLAW_GATEWAY_BIND:-lan}", "--port", "${OPENCLAW_GATEWAY_PORT:-18789}"] diff --git a/docker_openclaw/work/openclaw-plugin-installer.js b/docker_openclaw/work/openclaw-plugin-installer.js index 088bcb8..fc06ca6 100644 --- a/docker_openclaw/work/openclaw-plugin-installer.js +++ b/docker_openclaw/work/openclaw-plugin-installer.js @@ -5,6 +5,20 @@ const fs = require('fs'); const path = require('path'); const { spawnSync } = require('child_process'); +const DEFAULT_CONFIG = { + agents: { + defaults: { + workspace: '/opt/openclaw/data/workspace' + } + }, + gateway: { + controlUi: { + dangerouslyAllowHostHeaderOriginFallback: true, + dangerouslyDisableDeviceAuth: true + } + } +}; + function parseArgs(argv) { const args = { _: [] }; for (let i = 0; i < argv.length; i += 1) { @@ -44,6 +58,10 @@ function parseCommaList(value) { .filter(Boolean); } +function pluginIdFromPackage(packageName) { + return packageName.split('/').pop(); +} + function ensureConfigShape(config) { if (!config.plugins) config.plugins = {}; if (!Array.isArray(config.plugins.allow)) config.plugins.allow = []; @@ -59,31 +77,42 @@ function saveConfig(configPath, config) { fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); } -function pluginDirectoryCandidates(homeClaw, pluginDirName, extensionsDir) { - const roots = new Set(); - const normalizedHome = homeClaw && path.resolve(homeClaw); +function ensureConfigFile(configPath, templatePath) { + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + if (fs.existsSync(configPath) && fs.statSync(configPath).size > 0) { + return; + } - if (extensionsDir) roots.add(path.resolve(extensionsDir)); - if (normalizedHome) { - roots.add(path.join(normalizedHome, 'extensions')); - roots.add(path.join(normalizedHome, '.openclaw', 'extensions')); + if (templatePath && fs.existsSync(templatePath)) { + fs.copyFileSync(templatePath, configPath); + return; } - roots.add(path.join('/opt/openclaw', '.openclaw', 'extensions')); - return Array.from(roots).map((root) => path.join(root, pluginDirName)); + saveConfig(configPath, DEFAULT_CONFIG); } -function pluginDirectoryExists(homeClaw, pluginDirName, extensionsDir) { - const candidates = pluginDirectoryCandidates(homeClaw, pluginDirName, extensionsDir); - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - return { exists: true, path: candidate }; - } +function mergeDefaultConfig(configPath) { + const config = loadConfig(configPath); + + if (!config.agents) config.agents = {}; + if (!config.agents.defaults) config.agents.defaults = {}; + if (!config.agents.defaults.workspace) { + config.agents.defaults.workspace = DEFAULT_CONFIG.agents.defaults.workspace; } - return { exists: false, path: '' }; + + if (!config.gateway) config.gateway = {}; + if (!config.gateway.controlUi) config.gateway.controlUi = {}; + if (typeof config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback !== 'boolean') { + config.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true; + } + if (typeof config.gateway.controlUi.dangerouslyDisableDeviceAuth !== 'boolean') { + config.gateway.controlUi.dangerouslyDisableDeviceAuth = true; + } + + saveConfig(configPath, config); } -function installOpenclawPlugin(packageName, env) { +function runOpenclawInstall(packageName, env) { const result = spawnSync('openclaw', ['plugins', 'install', packageName], { encoding: 'utf8', env @@ -93,12 +122,12 @@ function installOpenclawPlugin(packageName, env) { if (result.stderr) process.stderr.write(result.stderr); if (result.status === 0) { - return { installed: true, alreadyExists: false }; + return { ok: true, alreadyExists: false }; } const output = `${result.stdout || ''}\n${result.stderr || ''}`; if (output.includes('plugin already exists')) { - return { installed: false, alreadyExists: true }; + return { ok: true, alreadyExists: true }; } const err = new Error(`Command failed: openclaw plugins install ${packageName}`); @@ -107,71 +136,177 @@ function installOpenclawPlugin(packageName, env) { throw err; } -function applyPluginConfig(config, pluginId, disableEntries) { +function safeRemove(targetPath) { + if (!fs.existsSync(targetPath)) return; + const stat = fs.lstatSync(targetPath); + if (stat.isDirectory() && !stat.isSymbolicLink()) { + fs.rmSync(targetPath, { recursive: true, force: true }); + } else { + fs.unlinkSync(targetPath); + } +} + +function ensurePluginAtTarget(options) { + const { homeClaw, stateDir, extensionsDir, pluginDirName } = options; + const targetPath = path.join(extensionsDir, pluginDirName); + + if (fs.existsSync(targetPath)) { + return targetPath; + } + + const candidates = [ + path.join(stateDir, '.openclaw', 'extensions', pluginDirName), + path.join(homeClaw, '.openclaw', 'extensions', pluginDirName), + path.join('/opt/openclaw', '.openclaw', 'extensions', pluginDirName), + path.join(homeClaw, 'extensions', pluginDirName) + ]; + + const actualPath = candidates.find((candidate) => fs.existsSync(candidate)); + if (!actualPath) { + return ''; + } + + fs.mkdirSync(path.dirname(targetPath), { recursive: true }); + + try { + fs.renameSync(actualPath, targetPath); + } catch (err) { + if (!err || err.code !== 'EXDEV') throw err; + fs.cpSync(actualPath, targetPath, { recursive: true }); + safeRemove(actualPath); + } + + fs.mkdirSync(path.dirname(actualPath), { recursive: true }); + safeRemove(actualPath); + fs.symlinkSync(targetPath, actualPath, 'dir'); + + return targetPath; +} + +function applyInstallConfig(configPath, pluginId) { + const config = loadConfig(configPath); ensureConfigShape(config); if (!config.plugins.allow.includes(pluginId)) { config.plugins.allow.push(pluginId); } - config.plugins.entries[pluginId] = { enabled: true }; - for (const entryId of disableEntries) { + saveConfig(configPath, config); +} + +function applyDisableConfig(configPath, entryIds) { + const config = loadConfig(configPath); + ensureConfigShape(config); + + for (const entryId of entryIds) { config.plugins.entries[entryId] = { enabled: false }; } + + saveConfig(configPath, config); } -function installPlugin(options) { - const { - homeClaw, - configPath, - packageName, - pluginId, - pluginDirName, - forceInstall, - stateDir, - extensionsDir, - disableEntries - } = options; +function installCommand(args) { + const packageName = args.package; + const configPath = args.config; + const homeClaw = path.resolve(args.home || '/opt/openclaw'); + const stateDir = path.resolve(args['state-dir'] || path.dirname(configPath || '')); + const extensionsDir = path.resolve(args['extensions-dir'] || path.join(stateDir, 'extensions')); + const pluginId = args.id || pluginIdFromPackage(packageName || ''); + const pluginDirName = args['plugin-dir'] || pluginId; + const forceInstall = Boolean(args.force); + const templatePath = args.template || ''; + + if (!packageName || !configPath) { + throw new Error('Missing required args: --package, --config'); + } + + ensureConfigFile(configPath, templatePath); + mergeDefaultConfig(configPath); fs.mkdirSync(stateDir, { recursive: true }); fs.mkdirSync(extensionsDir, { recursive: true }); + const targetPath = path.join(extensionsDir, pluginDirName); const installEnv = { ...process.env, OPENCLAW_DIR_STATE: stateDir, OPENCLAW_DIR_EXTENSIONS: extensionsDir, - XDG_CONFIG_HOME: stateDir + XDG_CONFIG_HOME: stateDir, + OPENCLAW_EXTENSIONS_DIR: extensionsDir }; - const existence = pluginDirectoryExists(homeClaw, pluginDirName, extensionsDir); - const needInstall = forceInstall || !existence.exists; - - if (!needInstall) { - console.log(`[plugin-installer] ${pluginId} already exists at ${existence.path}, skip install.`); - } else { + if (forceInstall || !fs.existsSync(targetPath)) { console.log(`[plugin-installer] Installing ${packageName} ...`); - const installResult = installOpenclawPlugin(packageName, installEnv); - if (installResult.alreadyExists) { + const result = runOpenclawInstall(packageName, installEnv); + if (result.alreadyExists) { console.log(`[plugin-installer] ${pluginId} already exists, treat as idempotent success.`); } + } else { + console.log(`[plugin-installer] ${pluginId} already exists at ${targetPath}, skip install.`); + } + + const finalPath = ensurePluginAtTarget({ homeClaw, stateDir, extensionsDir, pluginDirName }); + if (!finalPath) { + console.warn(`[plugin-installer] WARN: unable to locate installed plugin ${pluginId}.`); + } else { + console.log(`[plugin-installer] plugin path: ${finalPath}`); + } + + applyInstallConfig(configPath, pluginId); + console.log(`[plugin-installer] ${packageName} install flow completed.`); +} + +function disableCommand(args) { + const configPath = args.config; + const entryIds = parseCommaList(args.entry || args.entries); + + if (!configPath || entryIds.length === 0) { + throw new Error('Missing required args: --config, --entry '); } - const updated = loadConfig(configPath); - applyPluginConfig(updated, pluginId, disableEntries); - saveConfig(configPath, updated); + ensureConfigFile(configPath, args.template || ''); + mergeDefaultConfig(configPath); + applyDisableConfig(configPath, entryIds); + console.log(`[plugin-installer] disabled entries: ${entryIds.join(', ')}`); +} - const targetPluginPath = path.join(extensionsDir, pluginDirName); - if (!fs.existsSync(targetPluginPath)) { - const actual = pluginDirectoryExists(homeClaw, pluginDirName, extensionsDir); - console.warn(`[plugin-installer] WARN: expected plugin under ${targetPluginPath}, actual: ${actual.path || 'not found'}`); +function initConfigCommand(args) { + const configPath = args.config; + if (!configPath) { + throw new Error('Missing required args: --config'); } - console.log(`[plugin-installer] ${packageName} installed successfully.`); + ensureConfigFile(configPath, args.template || ''); + mergeDefaultConfig(configPath); + console.log(`[plugin-installer] config ready: ${configPath}`); } function printHelp() { - console.log(`Usage:\n node openclaw-plugin-installer.js install --package --id [options]\n\nOptions:\n --home OpenClaw home path (default: /opt/openclaw)\n --config OpenClaw config path (required)\n --state-dir OpenClaw state dir (default: dirname(--config))\n --extensions-dir Plugin install dir (default: /extensions)\n --plugin-dir Plugin directory name (default: --id value)\n --disable-entry Comma separated plugin entry ids to disable\n --force Force install even if plugin dir exists\n`); + console.log(`Usage: + node openclaw-plugin-installer.js [options] + +Commands: + init-config Ensure config file exists and apply default base settings + install Install plugin package and enable corresponding plugin entry + disable Disable plugin entries in config + +Common options: + --config OpenClaw config path + --template Config template path (optional) + +Install options: + --package NPM package, e.g. @larksuite/openclaw-lark + --id Optional plugin id (default: package basename) + --home OpenClaw home (default: /opt/openclaw) + --state-dir State dir (default: dirname(--config)) + --extensions-dir Extensions dir (default: /extensions) + --plugin-dir Plugin directory name (default: plugin id) + --force Force install even if target plugin dir exists + +Disable options: + --entry Comma separated plugin entry ids to disable +`); } function main() { @@ -183,39 +318,30 @@ function main() { process.exit(command ? 0 : 1); } - if (command !== 'install') { - console.error(`[plugin-installer] Unsupported command: ${command}`); - printHelp(); - process.exit(1); + if (command === 'init-config') { + initConfigCommand(args); + return; } - const packageName = args.package; - const pluginId = args.id; - const configPath = args.config; - const homeClaw = args.home || '/opt/openclaw'; - const stateDir = path.resolve(args['state-dir'] || path.dirname(configPath || '')); - const extensionsDir = path.resolve(args['extensions-dir'] || path.join(stateDir, 'extensions')); - const pluginDirName = args['plugin-dir'] || pluginId; - const forceInstall = Boolean(args.force); - const disableEntries = parseCommaList(args['disable-entry']); + if (command === 'install') { + installCommand(args); + return; + } - if (!packageName || !pluginId || !configPath) { - console.error('[plugin-installer] Missing required args: --package, --id, --config'); - printHelp(); - process.exit(1); - } - - installPlugin({ - homeClaw, - configPath, - packageName, - pluginId, - pluginDirName, - forceInstall, - stateDir, - extensionsDir, - disableEntries - }); + if (command === 'disable') { + disableCommand(args); + return; + } + + throw new Error(`Unsupported command: ${command}`); } -main(); +try { + main(); +} catch (err) { + console.error(`[plugin-installer] ${err.message}`); + if (err.output) { + console.error(err.output); + } + process.exit(1); +} diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index d8b621f..d823095 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,55 +1,40 @@ #!/bin/sh set -eu -HOME_CLAW="${HOME:-$HOME_CLAW}" +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +HOME_CLAW="${OPENCLAW_HOME:-$SCRIPT_DIR}" DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$HOME_CLAW/data}}" PATH_CONFIG="${DIR_STATE}/openclaw.json" -PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$HOME_CLAW/openclaw.template.json}" -PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$HOME_CLAW/openclaw-plugin-installer.js}" - - -ensure_config_file() { - mkdir -pv "${DIR_STATE}" - - if [ ! -s "${PATH_CONFIG}" ]; then - if [ -f "${PATH_TEMPLATE}" ]; then - cp "${PATH_TEMPLATE}" "${PATH_CONFIG}" - else - cat >"${PATH_CONFIG}" <<'JSON' -{ - "agents": { - "defaults": { - "workspace": "/opt/openclaw/data/workspace" - } - }, - "gateway": { - "controlUi": { - "dangerouslyAllowHostHeaderOriginFallback": true, - "dangerouslyDisableDeviceAuth": true - } - } -} -JSON - fi - fi -} +PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$SCRIPT_DIR/openclaw.template.json}" +PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$SCRIPT_DIR/openclaw-plugin-installer.js}" + + bootstrap() { export PATH_CONFIG export OPENCLAW_DIR_STATE="${DIR_STATE}" export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" export XDG_CONFIG_HOME="${OPENCLAW_DIR_STATE}" + + mkdir -pv "${DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" + openclaw config set skills.install.nodeManager pnpm - ensure_config_file + node "${PATH_PLUGIN_INSTALLER}" init-config \ + --config "${PATH_CONFIG}" \ + --template "${PATH_TEMPLATE}" + node "${PATH_PLUGIN_INSTALLER}" install \ --home "${HOME_CLAW}" \ --config "${PATH_CONFIG}" \ + --template "${PATH_TEMPLATE}" \ --state-dir "${OPENCLAW_DIR_STATE}" \ --extensions-dir "${OPENCLAW_DIR_EXTENSIONS}" \ - --package "@larksuite/openclaw-lark" \ - --id "openclaw-lark" \ - --disable-entry "feishu" + --package "@larksuite/openclaw-lark" + + node "${PATH_PLUGIN_INSTALLER}" disable \ + --config "${PATH_CONFIG}" \ + --entry "feishu" } bootstrap From 1fbdcf56a20b06230346339db9115cd393b9148d Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 21:06:28 +0800 Subject: [PATCH 09/14] debug paths --- docker_openclaw/openclaw.Dockerfile | 7 ++++--- docker_openclaw/work/start-openclaw.sh | 18 ++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 6da2db3..64bf3d8 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -7,7 +7,8 @@ FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} LABEL maintainer="postmaster@labnow.ai" ENV NODE_ENV=production ENV PNPM_HOME=/opt/node/pnpm -ENV PNPM_STORE_DIR=/opt/node/pnpm-store +ENV PNPM_STORE_PATH=/opt/node/pnpm-store +ENV PATH="${PNPM_HOME}:${PATH}" COPY work /opt/openclaw/ @@ -17,9 +18,9 @@ RUN set -eux && source /opt/utils/script-setup.sh \ && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ && setup_node_pnpm 10 && source /etc/profile.d/path-pnpm.sh \ && export PNPM_HOME=/opt/node/pnpm \ - && export PNPM_STORE_DIR=/opt/node/pnpm-store \ + && export PNPM_STORE_PATH=/opt/node/pnpm-store \ && export PATH="${PNPM_HOME}:${PATH}" \ - && mkdir -p ${PNPM_HOME} ${PNPM_STORE_DIR} \ + && mkdir -pv ${PNPM_HOME} ${PNPM_STORE_PATH} \ && pnpm install -g openclaw@latest \ && openclaw --version \ ## Clean up and display components version information... diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index d823095..73dfead 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,22 +1,20 @@ #!/bin/sh set -eu -SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" -HOME_CLAW="${OPENCLAW_HOME:-$SCRIPT_DIR}" -DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$HOME_CLAW/data}}" -PATH_CONFIG="${DIR_STATE}/openclaw.json" -PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$SCRIPT_DIR/openclaw.template.json}" -PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$SCRIPT_DIR/openclaw-plugin-installer.js}" - +OPENCLAW_HOME="${OPENCLAW_HOME:-$HOME}" +OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$OPENCLAW_HOME/data}}" +PATH_CONFIG="${OPENCLAW_DIR_STATE}/openclaw.json" +PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$OPENCLAW_HOME/openclaw.template.json}" +PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$OPENCLAW_HOME/openclaw-plugin-installer.js}" bootstrap() { export PATH_CONFIG - export OPENCLAW_DIR_STATE="${DIR_STATE}" + export OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE}" export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" export XDG_CONFIG_HOME="${OPENCLAW_DIR_STATE}" - mkdir -pv "${DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" + mkdir -pv "${OPENCLAW_DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" openclaw config set skills.install.nodeManager pnpm @@ -25,7 +23,7 @@ bootstrap() { --template "${PATH_TEMPLATE}" node "${PATH_PLUGIN_INSTALLER}" install \ - --home "${HOME_CLAW}" \ + --home "${OPENCLAW_HOME}" \ --config "${PATH_CONFIG}" \ --template "${PATH_TEMPLATE}" \ --state-dir "${OPENCLAW_DIR_STATE}" \ From 2a26e22bf96d8947b9f517c23aafb4bdd3bbb748 Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 22:03:52 +0800 Subject: [PATCH 10/14] debug --- .codex | 0 docker_openclaw/demo/docker-compose.yml | 2 + docker_openclaw/openclaw.Dockerfile | 8 +- .../work/openclaw-plugin-installer.js | 88 +++++++++++++------ docker_openclaw/work/openclaw.template.json | 40 --------- docker_openclaw/work/start-openclaw.sh | 20 ++--- 6 files changed, 75 insertions(+), 83 deletions(-) create mode 100644 .codex delete mode 100644 docker_openclaw/work/openclaw.template.json diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/docker_openclaw/demo/docker-compose.yml b/docker_openclaw/demo/docker-compose.yml index 184438b..63ea523 100644 --- a/docker_openclaw/demo/docker-compose.yml +++ b/docker_openclaw/demo/docker-compose.yml @@ -9,6 +9,7 @@ services: restart: unless-stopped environment: - TZ=Asia/Shanghai + - PROFILE_LOCALIZE=aliyun-pub volumes: - /data/openclaw:/opt/openclaw/data ports: @@ -24,6 +25,7 @@ services: restart: "no" environment: - TZ=Asia/Shanghai + - PROFILE_LOCALIZE=aliyun-pub - BROWSER=echo volumes: - /data/openclaw:/opt/openclaw/data diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 64bf3d8..a4bfe9b 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -14,13 +14,11 @@ COPY work /opt/openclaw/ RUN set -eux && source /opt/utils/script-setup.sh \ && chmod +x /opt/openclaw/start-openclaw.sh && ln -sf /opt/openclaw/start-openclaw.sh /usr/local/bin/ \ + && mkdir -pv /opt/openclaw/data \ + && ln -sfn /opt/openclaw/data /opt/openclaw/.openclaw \ ## curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ - && setup_node_pnpm 10 && source /etc/profile.d/path-pnpm.sh \ - && export PNPM_HOME=/opt/node/pnpm \ - && export PNPM_STORE_PATH=/opt/node/pnpm-store \ - && export PATH="${PNPM_HOME}:${PATH}" \ - && mkdir -pv ${PNPM_HOME} ${PNPM_STORE_PATH} \ + && setup_node_pnpm 10 \ && pnpm install -g openclaw@latest \ && openclaw --version \ ## Clean up and display components version information... diff --git a/docker_openclaw/work/openclaw-plugin-installer.js b/docker_openclaw/work/openclaw-plugin-installer.js index fc06ca6..d799b1a 100644 --- a/docker_openclaw/work/openclaw-plugin-installer.js +++ b/docker_openclaw/work/openclaw-plugin-installer.js @@ -5,19 +5,46 @@ const fs = require('fs'); const path = require('path'); const { spawnSync } = require('child_process'); -const DEFAULT_CONFIG = { - agents: { - defaults: { - workspace: '/opt/openclaw/data/workspace' +function buildDefaultConfig() { + return { + agents: { + defaults: { + workspace: '/opt/openclaw/data/workspace' + } + }, + channels: { + feishu: { + enabled: true, + appId: '', + appSecret: '', + domain: 'feishu', + connectionMode: 'websocket', + requireMention: true, + dmPolicy: 'pairing', + groupPolicy: 'open', + allowFrom: [], + groupAllowFrom: [] + } + }, + plugins: { + allow: ['openclaw-lark'], + entries: { + feishu: { + enabled: false + }, + 'openclaw-lark': { + enabled: true + } + } + }, + gateway: { + controlUi: { + dangerouslyAllowHostHeaderOriginFallback: true, + dangerouslyDisableDeviceAuth: true + } } - }, - gateway: { - controlUi: { - dangerouslyAllowHostHeaderOriginFallback: true, - dangerouslyDisableDeviceAuth: true - } - } -}; + }; +} function parseArgs(argv) { const args = { _: [] }; @@ -77,18 +104,12 @@ function saveConfig(configPath, config) { fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8'); } -function ensureConfigFile(configPath, templatePath) { +function ensureConfigFile(configPath) { fs.mkdirSync(path.dirname(configPath), { recursive: true }); if (fs.existsSync(configPath) && fs.statSync(configPath).size > 0) { return; } - - if (templatePath && fs.existsSync(templatePath)) { - fs.copyFileSync(templatePath, configPath); - return; - } - - saveConfig(configPath, DEFAULT_CONFIG); + saveConfig(configPath, buildDefaultConfig()); } function mergeDefaultConfig(configPath) { @@ -97,7 +118,7 @@ function mergeDefaultConfig(configPath) { if (!config.agents) config.agents = {}; if (!config.agents.defaults) config.agents.defaults = {}; if (!config.agents.defaults.workspace) { - config.agents.defaults.workspace = DEFAULT_CONFIG.agents.defaults.workspace; + config.agents.defaults.workspace = '/opt/openclaw/data/workspace'; } if (!config.gateway) config.gateway = {}; @@ -146,11 +167,25 @@ function safeRemove(targetPath) { } } +function ensureAliasLink(aliasPath, targetPath) { + if (!aliasPath || path.resolve(aliasPath) === path.resolve(targetPath)) return; + fs.mkdirSync(path.dirname(aliasPath), { recursive: true }); + safeRemove(aliasPath); + fs.symlinkSync(targetPath, aliasPath, 'dir'); +} + function ensurePluginAtTarget(options) { const { homeClaw, stateDir, extensionsDir, pluginDirName } = options; const targetPath = path.join(extensionsDir, pluginDirName); + const aliasPaths = [ + path.join(homeClaw, '.openclaw', 'extensions', pluginDirName), + path.join('/opt/openclaw', '.openclaw', 'extensions', pluginDirName) + ]; if (fs.existsSync(targetPath)) { + for (const aliasPath of aliasPaths) { + ensureAliasLink(aliasPath, targetPath); + } return targetPath; } @@ -179,6 +214,9 @@ function ensurePluginAtTarget(options) { fs.mkdirSync(path.dirname(actualPath), { recursive: true }); safeRemove(actualPath); fs.symlinkSync(targetPath, actualPath, 'dir'); + for (const aliasPath of aliasPaths) { + ensureAliasLink(aliasPath, targetPath); + } return targetPath; } @@ -215,13 +253,12 @@ function installCommand(args) { const pluginId = args.id || pluginIdFromPackage(packageName || ''); const pluginDirName = args['plugin-dir'] || pluginId; const forceInstall = Boolean(args.force); - const templatePath = args.template || ''; if (!packageName || !configPath) { throw new Error('Missing required args: --package, --config'); } - ensureConfigFile(configPath, templatePath); + ensureConfigFile(configPath); mergeDefaultConfig(configPath); fs.mkdirSync(stateDir, { recursive: true }); @@ -265,7 +302,7 @@ function disableCommand(args) { throw new Error('Missing required args: --config, --entry '); } - ensureConfigFile(configPath, args.template || ''); + ensureConfigFile(configPath); mergeDefaultConfig(configPath); applyDisableConfig(configPath, entryIds); console.log(`[plugin-installer] disabled entries: ${entryIds.join(', ')}`); @@ -277,7 +314,7 @@ function initConfigCommand(args) { throw new Error('Missing required args: --config'); } - ensureConfigFile(configPath, args.template || ''); + ensureConfigFile(configPath); mergeDefaultConfig(configPath); console.log(`[plugin-installer] config ready: ${configPath}`); } @@ -293,7 +330,6 @@ Commands: Common options: --config OpenClaw config path - --template Config template path (optional) Install options: --package NPM package, e.g. @larksuite/openclaw-lark diff --git a/docker_openclaw/work/openclaw.template.json b/docker_openclaw/work/openclaw.template.json deleted file mode 100644 index 548574c..0000000 --- a/docker_openclaw/work/openclaw.template.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "agents": { - "defaults": { - "workspace": "/opt/openclaw/data/workspace" - } - }, - "channels": { - "feishu": { - "enabled": true, - "appId": "", - "appSecret": "", - "domain": "feishu", - "connectionMode": "websocket", - "requireMention": true, - "dmPolicy": "pairing", - "groupPolicy": "open", - "allowFrom": [], - "groupAllowFrom": [] - } - }, - "plugins": { - "allow": [ - "openclaw-lark" - ], - "entries": { - "feishu": { - "enabled": false - }, - "openclaw-lark": { - "enabled": true - } - } - }, - "gateway": { - "controlUi": { - "dangerouslyAllowHostHeaderOriginFallback": true, - "dangerouslyDisableDeviceAuth": true - } - } -} diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index 73dfead..2a30f4c 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,39 +1,35 @@ #!/bin/sh set -eu -OPENCLAW_HOME="${OPENCLAW_HOME:-$HOME}" +OPENCLAW_HOME="${OPENCLAW_HOME:-/opt/openclaw}" OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$OPENCLAW_HOME/data}}" -PATH_CONFIG="${OPENCLAW_DIR_STATE}/openclaw.json" -PATH_TEMPLATE="${OPENCLAW_CONFIG_TEMPLATE:-$OPENCLAW_HOME/openclaw.template.json}" +OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-$OPENCLAW_HOME/.openclaw/openclaw.json}" PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$OPENCLAW_HOME/openclaw-plugin-installer.js}" bootstrap() { - export PATH_CONFIG + export OPENCLAW_CONFIG export OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE}" export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" - export XDG_CONFIG_HOME="${OPENCLAW_DIR_STATE}" - - mkdir -pv "${OPENCLAW_DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" + mkdir -pv "${OPENCLAW_DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" "$(dirname "${OPENCLAW_CONFIG}")" openclaw config set skills.install.nodeManager pnpm node "${PATH_PLUGIN_INSTALLER}" init-config \ - --config "${PATH_CONFIG}" \ - --template "${PATH_TEMPLATE}" + --config "${OPENCLAW_CONFIG}" node "${PATH_PLUGIN_INSTALLER}" install \ --home "${OPENCLAW_HOME}" \ - --config "${PATH_CONFIG}" \ - --template "${PATH_TEMPLATE}" \ + --config "${OPENCLAW_CONFIG}" \ --state-dir "${OPENCLAW_DIR_STATE}" \ --extensions-dir "${OPENCLAW_DIR_EXTENSIONS}" \ --package "@larksuite/openclaw-lark" node "${PATH_PLUGIN_INSTALLER}" disable \ - --config "${PATH_CONFIG}" \ + --config "${OPENCLAW_CONFIG}" \ --entry "feishu" } +/opt/utils/script-localize.sh "${PROFILE_LOCALIZE:-default}" bootstrap exec openclaw "$@" From 4edc48098d4177fef61d76d8ec514c8430f7f684 Mon Sep 17 00:00:00 2001 From: haobibo Date: Tue, 31 Mar 2026 23:27:53 +0800 Subject: [PATCH 11/14] debug --- .../work/openclaw-plugin-installer.js | 55 +++++++------------ docker_openclaw/work/start-openclaw.sh | 30 +++------- 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/docker_openclaw/work/openclaw-plugin-installer.js b/docker_openclaw/work/openclaw-plugin-installer.js index d799b1a..23bb238 100644 --- a/docker_openclaw/work/openclaw-plugin-installer.js +++ b/docker_openclaw/work/openclaw-plugin-installer.js @@ -26,17 +26,6 @@ function buildDefaultConfig() { groupAllowFrom: [] } }, - plugins: { - allow: ['openclaw-lark'], - entries: { - feishu: { - enabled: false - }, - 'openclaw-lark': { - enabled: true - } - } - }, gateway: { controlUi: { dangerouslyAllowHostHeaderOriginFallback: true, @@ -167,39 +156,44 @@ function safeRemove(targetPath) { } } -function ensureAliasLink(aliasPath, targetPath) { - if (!aliasPath || path.resolve(aliasPath) === path.resolve(targetPath)) return; - fs.mkdirSync(path.dirname(aliasPath), { recursive: true }); - safeRemove(aliasPath); - fs.symlinkSync(targetPath, aliasPath, 'dir'); +function isSelfReferencingSymlink(targetPath) { + try { + const stat = fs.lstatSync(targetPath); + if (!stat.isSymbolicLink()) return false; + const linkValue = fs.readlinkSync(targetPath); + const resolved = path.resolve(path.dirname(targetPath), linkValue); + return resolved === path.resolve(targetPath); + } catch (_err) { + return false; + } } function ensurePluginAtTarget(options) { const { homeClaw, stateDir, extensionsDir, pluginDirName } = options; const targetPath = path.join(extensionsDir, pluginDirName); - const aliasPaths = [ - path.join(homeClaw, '.openclaw', 'extensions', pluginDirName), - path.join('/opt/openclaw', '.openclaw', 'extensions', pluginDirName) - ]; + const resolvedTargetPath = path.resolve(targetPath); + + if (isSelfReferencingSymlink(targetPath)) { + safeRemove(targetPath); + } if (fs.existsSync(targetPath)) { - for (const aliasPath of aliasPaths) { - ensureAliasLink(aliasPath, targetPath); - } return targetPath; } const candidates = [ - path.join(stateDir, '.openclaw', 'extensions', pluginDirName), - path.join(homeClaw, '.openclaw', 'extensions', pluginDirName), - path.join('/opt/openclaw', '.openclaw', 'extensions', pluginDirName), - path.join(homeClaw, 'extensions', pluginDirName) + path.join(homeClaw, 'extensions', pluginDirName), + path.join(stateDir, 'extensions', pluginDirName), ]; const actualPath = candidates.find((candidate) => fs.existsSync(candidate)); if (!actualPath) { return ''; } + const resolvedActualPath = path.resolve(actualPath); + if (resolvedActualPath === resolvedTargetPath) { + return targetPath; + } fs.mkdirSync(path.dirname(targetPath), { recursive: true }); @@ -211,13 +205,6 @@ function ensurePluginAtTarget(options) { safeRemove(actualPath); } - fs.mkdirSync(path.dirname(actualPath), { recursive: true }); - safeRemove(actualPath); - fs.symlinkSync(targetPath, actualPath, 'dir'); - for (const aliasPath of aliasPaths) { - ensureAliasLink(aliasPath, targetPath); - } - return targetPath; } diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index 2a30f4c..e4871c1 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -1,33 +1,21 @@ #!/bin/sh set -eu -OPENCLAW_HOME="${OPENCLAW_HOME:-/opt/openclaw}" -OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$OPENCLAW_HOME/data}}" -OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-$OPENCLAW_HOME/.openclaw/openclaw.json}" -PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$OPENCLAW_HOME/openclaw-plugin-installer.js}" +export OPENCLAW_HOME="${OPENCLAW_HOME:-/opt/openclaw}" +export OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$OPENCLAW_HOME/data}}" +export OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-$OPENCLAW_DIR_STATE/openclaw.json}" +export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" bootstrap() { - export OPENCLAW_CONFIG - export OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE}" - export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" mkdir -pv "${OPENCLAW_DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" "$(dirname "${OPENCLAW_CONFIG}")" - openclaw config set skills.install.nodeManager pnpm + local PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$OPENCLAW_HOME/openclaw-plugin-installer.js}" + local CLAW_EXEC="node ${PATH_PLUGIN_INSTALLER} --config "${OPENCLAW_CONFIG}" - node "${PATH_PLUGIN_INSTALLER}" init-config \ - --config "${OPENCLAW_CONFIG}" - - node "${PATH_PLUGIN_INSTALLER}" install \ - --home "${OPENCLAW_HOME}" \ - --config "${OPENCLAW_CONFIG}" \ - --state-dir "${OPENCLAW_DIR_STATE}" \ - --extensions-dir "${OPENCLAW_DIR_EXTENSIONS}" \ - --package "@larksuite/openclaw-lark" - - node "${PATH_PLUGIN_INSTALLER}" disable \ - --config "${OPENCLAW_CONFIG}" \ - --entry "feishu" + $CLAW_EXEC init-config + $CLAW_EXEC disable --entry "feishu" + $CLAW_EXEC install --package "@larksuite/openclaw-lark" } /opt/utils/script-localize.sh "${PROFILE_LOCALIZE:-default}" From a541dc57d40817c3e9f533d589e1832c8f828014 Mon Sep 17 00:00:00 2001 From: haobibo Date: Wed, 1 Apr 2026 00:06:31 +0800 Subject: [PATCH 12/14] debug claw pkgs --- docker_openclaw/README.md | 23 ------------------- docker_openclaw/demo/docker-compose.yml | 2 +- docker_openclaw/openclaw.Dockerfile | 4 ++-- .../work/openclaw-plugin-installer.js | 6 ++--- docker_openclaw/work/start-openclaw.sh | 10 ++++---- 5 files changed, 11 insertions(+), 34 deletions(-) delete mode 100644 docker_openclaw/README.md diff --git a/docker_openclaw/README.md b/docker_openclaw/README.md deleted file mode 100644 index c870ea4..0000000 --- a/docker_openclaw/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# OpenClaw - -`openclaw` image is built from `ghcr.io/openclaw/openclaw:latest` and adds: - -- source bootstrap and plugin setup -- default runtime config template -- startup script that auto-checks and installs `@larksuite/openclaw-lark` - -## Build Args - -- `OPENCLAW_GIT_URL`: source repo URL for build stage clone. -- `OPENCLAW_GIT_REF`: source branch or tag. -- `NPM_REGISTRY`: npm registry used by `pnpm`. - -## Runtime - -Default command: - -```bash -sh /usr/local/bin/bootstrap-openclaw.sh gateway --allow-unconfigured --bind lan --port 18789 -``` - -The startup script will create and normalize config under `/home/node/.openclaw/openclaw.json`. diff --git a/docker_openclaw/demo/docker-compose.yml b/docker_openclaw/demo/docker-compose.yml index 63ea523..e7cc0f1 100644 --- a/docker_openclaw/demo/docker-compose.yml +++ b/docker_openclaw/demo/docker-compose.yml @@ -29,7 +29,7 @@ services: - BROWSER=echo volumes: - /data/openclaw:/opt/openclaw/data + init: true stdin_open: true tty: true - init: true entrypoint: ["node", "openclaw.mjs"] diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index a4bfe9b..5796c6b 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -7,7 +7,8 @@ FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} LABEL maintainer="postmaster@labnow.ai" ENV NODE_ENV=production ENV PNPM_HOME=/opt/node/pnpm -ENV PNPM_STORE_PATH=/opt/node/pnpm-store +ENV PNPM_STORE_DIR=/opt/node/pnpm-store +ENV PNPM_NODE_LINKER=hoisted ENV PATH="${PNPM_HOME}:${PATH}" COPY work /opt/openclaw/ @@ -26,7 +27,6 @@ RUN set -eux && source /opt/utils/script-setup.sh \ ENV HOME=/opt/openclaw/ ENV XDG_CONFIG_HOME=/opt/openclaw/data -ENV OPENCLAW_HIDE_BANNER=1 WORKDIR /opt/openclaw VOLUME ["/opt/openclaw/data"] EXPOSE 18789 18790 diff --git a/docker_openclaw/work/openclaw-plugin-installer.js b/docker_openclaw/work/openclaw-plugin-installer.js index 23bb238..b5bcdcd 100644 --- a/docker_openclaw/work/openclaw-plugin-installer.js +++ b/docker_openclaw/work/openclaw-plugin-installer.js @@ -182,8 +182,8 @@ function ensurePluginAtTarget(options) { } const candidates = [ - path.join(homeClaw, 'extensions', pluginDirName), path.join(stateDir, 'extensions', pluginDirName), + path.join(homeClaw, 'extensions', pluginDirName), ]; const actualPath = candidates.find((candidate) => fs.existsSync(candidate)); @@ -254,10 +254,8 @@ function installCommand(args) { const targetPath = path.join(extensionsDir, pluginDirName); const installEnv = { ...process.env, - OPENCLAW_DIR_STATE: stateDir, - OPENCLAW_DIR_EXTENSIONS: extensionsDir, XDG_CONFIG_HOME: stateDir, - OPENCLAW_EXTENSIONS_DIR: extensionsDir + OPENCLAW_DIR_STATE: stateDir, }; if (forceInstall || !fs.existsSync(targetPath)) { diff --git a/docker_openclaw/work/start-openclaw.sh b/docker_openclaw/work/start-openclaw.sh index e4871c1..166d185 100644 --- a/docker_openclaw/work/start-openclaw.sh +++ b/docker_openclaw/work/start-openclaw.sh @@ -4,16 +4,18 @@ set -eu export OPENCLAW_HOME="${OPENCLAW_HOME:-/opt/openclaw}" export OPENCLAW_DIR_STATE="${OPENCLAW_DIR_STATE:-${XDG_CONFIG_HOME:-$OPENCLAW_HOME/data}}" export OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-$OPENCLAW_DIR_STATE/openclaw.json}" -export OPENCLAW_DIR_EXTENSIONS="${OPENCLAW_DIR_EXTENSIONS:-${OPENCLAW_DIR_STATE}/extensions}" +export OPENCLAW_HIDE_BANNER=1 bootstrap() { - mkdir -pv "${OPENCLAW_DIR_STATE}" "${OPENCLAW_DIR_EXTENSIONS}" "$(dirname "${OPENCLAW_CONFIG}")" + mkdir -pv "${OPENCLAW_DIR_STATE}" "$(dirname "${OPENCLAW_CONFIG}")" - local PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$OPENCLAW_HOME/openclaw-plugin-installer.js}" - local CLAW_EXEC="node ${PATH_PLUGIN_INSTALLER} --config "${OPENCLAW_CONFIG}" + local PATH_PLUGIN_INSTALLER="${OPENCLAW_PLUGIN_INSTALLER:-$OPENCLAW_HOME/openclaw-plugin-installer.js}" + local CLAW_EXEC="node ${PATH_PLUGIN_INSTALLER} --config ${OPENCLAW_CONFIG}" $CLAW_EXEC init-config + openclaw config set skills.install.nodeManager pnpm + $CLAW_EXEC disable --entry "feishu" $CLAW_EXEC install --package "@larksuite/openclaw-lark" } From d95198273806e63523edd9d86a35c0351ce7cb94 Mon Sep 17 00:00:00 2001 From: haobibo Date: Wed, 1 Apr 2026 00:15:10 +0800 Subject: [PATCH 13/14] update for pnpm install scripts --- docker_openclaw/openclaw.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker_openclaw/openclaw.Dockerfile b/docker_openclaw/openclaw.Dockerfile index 5796c6b..c569dd2 100644 --- a/docker_openclaw/openclaw.Dockerfile +++ b/docker_openclaw/openclaw.Dockerfile @@ -20,7 +20,8 @@ RUN set -eux && source /opt/utils/script-setup.sh \ ## curl -fsSL https://openclaw.ai/install.sh | NO_PROMPT=1 bash -s -- --no-onboard --install-method npm \ && export SHARP_IGNORE_GLOBAL_LIBVIPS=1 \ && setup_node_pnpm 10 \ - && pnpm install -g openclaw@latest \ + && pnpm config set enable-pre-post-scripts true \ + && pnpm install -g openclaw@latest --ignore-scripts=false \ && openclaw --version \ ## Clean up and display components version information... && list_installed_packages && install__clean From bfd6f18631b2586b28a14bbdc12729d84ae6747a Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Wed, 1 Apr 2026 00:55:56 +0800 Subject: [PATCH 14/14] Delete .codex --- .codex | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .codex diff --git a/.codex b/.codex deleted file mode 100644 index e69de29..0000000