fix(docker): restore config parent ownership

This commit is contained in:
sallyom
2026-05-25 00:30:56 -04:00
committed by Sally O'Malley
parent 3a03dd5712
commit 908b894432
5 changed files with 24 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
- Gateway/perf: tighten restart and startup benchmark failure handling so long profiling runs, failed probes, and fresh Linux runners no longer produce false passing or `n/a` results.
- Checks: keep intentional Knip unused-file findings optional so full CI and sparse proof workspaces stay aligned.
- Docker: restore writable `~/.config` in runtime images. Fixes #85968. Thanks @hkoessler and @Bartok9.
- Tests: normalize bundled plugin lifecycle probe paths and state-root lookup so native Windows release sweeps accept valid packaged plugin installs.
- Config: keep benign legacy metadata write anomalies out of default doctor and config command output while preserving explicit anomaly logging for diagnostics.
- Codex: log when implicit app-server `never` approvals are promoted for OpenClaw tool policy, including whether the trigger was a `before_tool_call` hook or trusted tool policy.

View File

@@ -287,12 +287,17 @@ RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
# Pre-create default named-volume mount points so first-run Docker volumes copy
# node ownership from the image instead of starting as root-owned directories.
RUN install -d -m 0700 -o node -g node \
# NOTE: /home/node/.config must be created with node ownership first so that
# the leaf /home/node/.config/openclaw inherits the correct parent permissions.
# Without this, install -d leaves /home/node/.config as root:root (issue #85968).
RUN install -d -m 0755 -o node -g node /home/node/.config && \
install -d -m 0700 -o node -g node \
/home/node/.openclaw \
/home/node/.openclaw/workspace \
/home/node/.config/openclaw && \
stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' && \
stat -c '%U:%G %a' /home/node/.openclaw/workspace | grep -qx 'node:node 700' && \
stat -c '%U:%G %a' /home/node/.config | grep -qx 'node:node 755' && \
stat -c '%U:%G %a' /home/node/.config/openclaw | grep -qx 'node:node 700'
ENV NODE_ENV=production

View File

@@ -554,6 +554,7 @@ echo "==> Fixing data-directory permissions"
# (.openclaw/) inside the workspace gets chowned, not the user's project files.
run_prestart_gateway --user root --entrypoint sh openclaw-gateway -c \
'find /home/node/.openclaw -xdev -exec chown node:node {} +; \
chown node:node /home/node/.config; \
find /home/node/.config/openclaw -xdev -exec chown node:node {} +; \
[ -d /home/node/.openclaw/workspace/.openclaw ] && chown -R node:node /home/node/.openclaw/workspace/.openclaw || true'

View File

@@ -482,6 +482,7 @@ describe("scripts/docker/setup.sh", () => {
expect(chownIdx).toBeGreaterThanOrEqual(0);
expect(onboardIdx).toBeGreaterThan(chownIdx);
expect(log).toContain("run --rm --no-deps --user root --entrypoint sh openclaw-gateway -c");
expect(log).toContain("chown node:node /home/node/.config");
});
it("precreates auth profile secret key dir outside the mounted state dir", async () => {

View File

@@ -327,15 +327,24 @@ describe("Dockerfile", () => {
it("pre-creates named-volume mount points before switching to the node user", async () => {
const dockerfile = await readFile(dockerfilePath, "utf8");
const runtimeStageIndex = dockerfile.lastIndexOf("FROM base-runtime");
const stateDirIndex = dockerfile.indexOf(
"RUN install -d -m 0700 -o node -g node \\",
const parentConfigDirIndex = dockerfile.indexOf(
"RUN install -d -m 0755 -o node -g node /home/node/.config",
runtimeStageIndex,
);
const stateDirIndex = dockerfile.indexOf(
"install -d -m 0700 -o node -g node \\",
parentConfigDirIndex,
);
const userIndex = dockerfile.indexOf("USER node", runtimeStageIndex);
expect(runtimeStageIndex).toBeGreaterThan(-1);
// Regression: /home/node/.config parent must be created with node ownership
// before the leaf .config/openclaw dir (issue #85968).
expect(parentConfigDirIndex).toBeGreaterThan(-1);
expect(stateDirIndex).toBeGreaterThan(-1);
expect(userIndex).toBeGreaterThan(-1);
expect(parentConfigDirIndex).toBeGreaterThan(runtimeStageIndex);
expect(parentConfigDirIndex).toBeLessThan(stateDirIndex);
expect(stateDirIndex).toBeGreaterThan(runtimeStageIndex);
expect(stateDirIndex).toBeLessThan(userIndex);
expect(dockerfile).not.toContain("mkdir -p /home/node/.openclaw");
@@ -347,6 +356,10 @@ describe("Dockerfile", () => {
expect(dockerfile).toContain(
"stat -c '%U:%G %a' /home/node/.openclaw/workspace | grep -qx 'node:node 700'",
);
// Regression: assert parent /home/node/.config is also node-owned (issue #85968).
expect(dockerfile).toContain(
"stat -c '%U:%G %a' /home/node/.config | grep -qx 'node:node 755'",
);
expect(dockerfile).toContain(
"stat -c '%U:%G %a' /home/node/.config/openclaw | grep -qx 'node:node 700'",
);