diff --git a/.github/workflows/sandbox-common-smoke.yml b/.github/workflows/sandbox-common-smoke.yml index ba31f4bba86..08e695c4490 100644 --- a/.github/workflows/sandbox-common-smoke.yml +++ b/.github/workflows/sandbox-common-smoke.yml @@ -57,11 +57,10 @@ jobs: BASE_IMAGE="openclaw-sandbox-smoke-base:bookworm-slim" \ TARGET_IMAGE="openclaw-sandbox-common-smoke:bookworm-slim" \ PACKAGES="ca-certificates" \ - INSTALL_PNPM=0 \ INSTALL_BUN=0 \ INSTALL_BREW=0 \ FINAL_USER=sandbox \ scripts/sandbox-common-setup.sh - u="$(timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')" - test "$u" = "sandbox" + timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc \ + 'set -e; test "$(id -un)" = sandbox; node --version; pnpm --version' diff --git a/docs/gateway/sandboxing.md b/docs/gateway/sandboxing.md index 8bf468d3072..f3503a0d8ae 100644 --- a/docs/gateway/sandboxing.md +++ b/docs/gateway/sandboxing.md @@ -415,7 +415,7 @@ If you installed OpenClaw via `npm install -g openclaw`, use the inline `docker - For a more functional sandbox image with common tooling (for example `curl`, `jq`, `nodejs`, `python3`, `git`): + For a more functional sandbox image with common tooling (for example `curl`, `jq`, Node 24, pnpm, `python3`, and `git`): From a source checkout: diff --git a/scripts/docker/sandbox/Dockerfile.common b/scripts/docker/sandbox/Dockerfile.common index 39eaa3692b4..789b8abc3e6 100644 --- a/scripts/docker/sandbox/Dockerfile.common +++ b/scripts/docker/sandbox/Dockerfile.common @@ -7,7 +7,9 @@ USER root ENV DEBIAN_FRONTEND=noninteractive -ARG PACKAGES="curl wget jq coreutils grep nodejs npm python3 git ca-certificates golang-go rustc cargo unzip pkg-config libasound2-dev build-essential file" +ARG PACKAGES="curl wget jq coreutils grep python3 git ca-certificates golang-go rustc cargo unzip pkg-config libasound2-dev build-essential file" +ARG INSTALL_NODE=1 +ARG NODE_MAJOR=24 ARG INSTALL_PNPM=1 ARG INSTALL_BUN=1 ARG BUN_INSTALL_DIR=/opt/bun @@ -26,7 +28,18 @@ RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/ap apt-get update \ && apt-get install -y --no-install-recommends ${PACKAGES} -RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi +RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,id=openclaw-sandbox-common-apt-lists,target=/var/lib/apt,sharing=locked \ + if [ "${INSTALL_NODE}" = "1" ]; then \ + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates curl; \ + curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -; \ + apt-get install -y --no-install-recommends nodejs; \ + node --version; \ + npm --version; \ + fi + +RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm && pnpm --version; fi RUN if [ "${INSTALL_BUN}" = "1" ]; then \ curl -fsSL https://bun.sh/install | bash; \ diff --git a/scripts/sandbox-common-setup.sh b/scripts/sandbox-common-setup.sh index 7d0655ba680..be653c3606f 100755 --- a/scripts/sandbox-common-setup.sh +++ b/scripts/sandbox-common-setup.sh @@ -6,7 +6,9 @@ source "$ROOT_DIR/scripts/lib/docker-build.sh" BASE_IMAGE="${BASE_IMAGE:-openclaw-sandbox:bookworm-slim}" TARGET_IMAGE="${TARGET_IMAGE:-openclaw-sandbox-common:bookworm-slim}" -PACKAGES="${PACKAGES:-curl wget jq coreutils grep nodejs npm python3 git ca-certificates golang-go rustc cargo unzip pkg-config libasound2-dev build-essential file}" +PACKAGES="${PACKAGES:-curl wget jq coreutils grep python3 git ca-certificates golang-go rustc cargo unzip pkg-config libasound2-dev build-essential file}" +INSTALL_NODE="${INSTALL_NODE:-1}" +NODE_MAJOR="${NODE_MAJOR:-24}" INSTALL_PNPM="${INSTALL_PNPM:-1}" INSTALL_BUN="${INSTALL_BUN:-1}" BUN_INSTALL_DIR="${BUN_INSTALL_DIR:-/opt/bun}" @@ -30,6 +32,8 @@ docker_build_exec \ -f "$ROOT_DIR/scripts/docker/sandbox/Dockerfile.common" \ --build-arg BASE_IMAGE="${BASE_IMAGE}" \ --build-arg PACKAGES="${PACKAGES}" \ + --build-arg INSTALL_NODE="${INSTALL_NODE}" \ + --build-arg NODE_MAJOR="${NODE_MAJOR}" \ --build-arg INSTALL_PNPM="${INSTALL_PNPM}" \ --build-arg INSTALL_BUN="${INSTALL_BUN}" \ --build-arg BUN_INSTALL_DIR="${BUN_INSTALL_DIR}" \ diff --git a/src/docker-build-cache.test.ts b/src/docker-build-cache.test.ts index c04de58f18f..c589ec0dd0c 100644 --- a/src/docker-build-cache.test.ts +++ b/src/docker-build-cache.test.ts @@ -92,8 +92,11 @@ describe("docker build cache layout", () => { it("does not leave empty shell continuation lines in sandbox-common", async () => { const dockerfile = await readRepoFile("scripts/docker/sandbox/Dockerfile.common"); expect(dockerfile).not.toContain("apt-get install -y --no-install-recommends ${PACKAGES} \\"); + expect(dockerfile).toContain("ARG INSTALL_NODE=1"); + expect(dockerfile).toContain("ARG NODE_MAJOR=24"); + expect(dockerfile).toContain('curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x"'); expect(dockerfile).toContain( - 'RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi', + 'RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm && pnpm --version; fi', ); }); diff --git a/test/scripts/sandbox-common-smoke-workflow.test.ts b/test/scripts/sandbox-common-smoke-workflow.test.ts index 2d76213978d..4916bbb98bb 100644 --- a/test/scripts/sandbox-common-smoke-workflow.test.ts +++ b/test/scripts/sandbox-common-smoke-workflow.test.ts @@ -14,6 +14,9 @@ describe("sandbox common smoke workflow", () => { expect(workflow).toContain( "timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim", ); + expect(workflow).toContain("node --version"); + expect(workflow).toContain("pnpm --version"); + expect(workflow).not.toContain("INSTALL_PNPM=0"); expect(workflow).not.toMatch(/(^|\n)\s+docker build -t openclaw-sandbox-smoke-base/u); expect(workflow).not.toContain( 'u="$(docker run --rm openclaw-sandbox-common-smoke:bookworm-slim',