Skip to content

Commit 659b5dc

Browse files
committed
fix(docker): package runtime workspace templates
1 parent 8e84459 commit 659b5dc

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

.github/workflows/docker-release.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,50 @@ jobs:
162162
provenance: mode=max
163163
push: true
164164

165+
- name: Smoke test amd64 runtime workspace templates
166+
shell: bash
167+
env:
168+
IMAGE_REFS: ${{ steps.tags.outputs.value }}
169+
run: |
170+
set -euo pipefail
171+
mapfile -t image_refs <<< "${IMAGE_REFS}"
172+
image_ref="${image_refs[0]}"
173+
if [[ -z "${image_ref}" ]]; then
174+
echo "::error::No amd64 image ref resolved for runtime template smoke"
175+
exit 1
176+
fi
177+
docker run --rm --entrypoint /bin/sh "${image_ref}" -lc '
178+
set -eu
179+
test -f /app/src/agents/templates/HEARTBEAT.md
180+
temp_root="$(mktemp -d)"
181+
trap "rm -rf \"${temp_root}\"" EXIT
182+
mkdir -p "${temp_root}/home" "${temp_root}/cwd"
183+
cd "${temp_root}/cwd"
184+
set +e
185+
HOME="${temp_root}/home" \
186+
USERPROFILE="${temp_root}/home" \
187+
OPENCLAW_HOME="${temp_root}/home" \
188+
OPENCLAW_NO_ONBOARD=1 \
189+
OPENCLAW_SUPPRESS_NOTES=1 \
190+
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 \
191+
OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK=1 \
192+
AWS_EC2_METADATA_DISABLED=true \
193+
AWS_SHARED_CREDENTIALS_FILE="${temp_root}/home/.aws/credentials" \
194+
AWS_CONFIG_FILE="${temp_root}/home/.aws/config" \
195+
node /app/openclaw.mjs agent --message "workspace bootstrap smoke" --session-id "workspace-bootstrap-smoke" --local --timeout 1 --json \
196+
>"${temp_root}/out.log" 2>&1
197+
status="$?"
198+
set -e
199+
if grep -F "Missing workspace template:" "${temp_root}/out.log"; then
200+
cat "${temp_root}/out.log"
201+
exit 1
202+
fi
203+
test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"
204+
if [ "${status}" -ne 0 ]; then
205+
cat "${temp_root}/out.log"
206+
fi
207+
'
208+
165209
# Build arm64 image. Default and slim tags point to the same slim runtime.
166210
build-arm64:
167211
needs: [approve_manual_backfill]
@@ -260,6 +304,50 @@ jobs:
260304
provenance: mode=max
261305
push: true
262306

307+
- name: Smoke test arm64 runtime workspace templates
308+
shell: bash
309+
env:
310+
IMAGE_REFS: ${{ steps.tags.outputs.value }}
311+
run: |
312+
set -euo pipefail
313+
mapfile -t image_refs <<< "${IMAGE_REFS}"
314+
image_ref="${image_refs[0]}"
315+
if [[ -z "${image_ref}" ]]; then
316+
echo "::error::No arm64 image ref resolved for runtime template smoke"
317+
exit 1
318+
fi
319+
docker run --rm --entrypoint /bin/sh "${image_ref}" -lc '
320+
set -eu
321+
test -f /app/src/agents/templates/HEARTBEAT.md
322+
temp_root="$(mktemp -d)"
323+
trap "rm -rf \"${temp_root}\"" EXIT
324+
mkdir -p "${temp_root}/home" "${temp_root}/cwd"
325+
cd "${temp_root}/cwd"
326+
set +e
327+
HOME="${temp_root}/home" \
328+
USERPROFILE="${temp_root}/home" \
329+
OPENCLAW_HOME="${temp_root}/home" \
330+
OPENCLAW_NO_ONBOARD=1 \
331+
OPENCLAW_SUPPRESS_NOTES=1 \
332+
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 \
333+
OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK=1 \
334+
AWS_EC2_METADATA_DISABLED=true \
335+
AWS_SHARED_CREDENTIALS_FILE="${temp_root}/home/.aws/credentials" \
336+
AWS_CONFIG_FILE="${temp_root}/home/.aws/config" \
337+
node /app/openclaw.mjs agent --message "workspace bootstrap smoke" --session-id "workspace-bootstrap-smoke" --local --timeout 1 --json \
338+
>"${temp_root}/out.log" 2>&1
339+
status="$?"
340+
set -e
341+
if grep -F "Missing workspace template:" "${temp_root}/out.log"; then
342+
cat "${temp_root}/out.log"
343+
exit 1
344+
fi
345+
test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"
346+
if [ "${status}" -ne 0 ]; then
347+
cat "${temp_root}/out.log"
348+
fi
349+
'
350+
263351
# Create multi-platform manifests
264352
create-manifest:
265353
needs: [approve_manual_backfill, build-amd64, build-arm64]

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ COPY --from=runtime-assets --chown=node:node /app/package.json .
178178
COPY --from=runtime-assets --chown=node:node /app/pnpm-workspace.yaml .
179179
COPY --from=runtime-assets --chown=node:node /app/patches ./patches
180180
COPY --from=runtime-assets --chown=node:node /app/openclaw.mjs .
181+
COPY --from=runtime-assets --chown=node:node /app/src/agents/templates ./src/agents/templates
181182
COPY --from=runtime-assets --chown=node:node /app/${OPENCLAW_BUNDLED_PLUGIN_DIR} ./${OPENCLAW_BUNDLED_PLUGIN_DIR}
182183
COPY --from=runtime-assets --chown=node:node /app/skills ./skills
183184
COPY --from=runtime-assets --chown=node:node /app/docs ./docs

src/dockerfile.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,20 @@ describe("Dockerfile", () => {
246246
);
247247
});
248248

249+
it("keeps runtime workspace templates in final images", async () => {
250+
const dockerfile = await readFile(dockerfilePath, "utf8");
251+
const runtimeStageIndex = dockerfile.lastIndexOf("FROM base-runtime");
252+
const templatesCopyIndex = dockerfile.indexOf(
253+
"COPY --from=runtime-assets --chown=node:node /app/src/agents/templates ./src/agents/templates",
254+
runtimeStageIndex,
255+
);
256+
const userIndex = dockerfile.indexOf("USER node", runtimeStageIndex);
257+
258+
expect(runtimeStageIndex).toBeGreaterThan(-1);
259+
expect(templatesCopyIndex).toBeGreaterThan(runtimeStageIndex);
260+
expect(templatesCopyIndex).toBeLessThan(userIndex);
261+
});
262+
249263
it("keeps package manager patch files in runtime images", async () => {
250264
const dockerfile = collapseDockerContinuations(await readFile(dockerfilePath, "utf8"));
251265
const pnpmWorkspace = YAML.parse(await readFile(pnpmWorkspacePath, "utf8")) as {
@@ -277,6 +291,16 @@ describe("Dockerfile", () => {
277291
expect(workflow).not.toContain("OPENCLAW_EXTENSIONS=diagnostics-otel\n");
278292
});
279293

294+
it("smokes runtime workspace templates before Docker release manifests publish", async () => {
295+
const workflow = await readFile(dockerReleaseWorkflowPath, "utf8");
296+
297+
expect(workflow).toContain("Smoke test amd64 runtime workspace templates");
298+
expect(workflow).toContain("Smoke test arm64 runtime workspace templates");
299+
expect(workflow).toContain("test -f /app/src/agents/templates/HEARTBEAT.md");
300+
expect(workflow).toContain('grep -F "Missing workspace template:"');
301+
expect(workflow).toContain('test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"');
302+
});
303+
280304
it("does not override bundled plugin discovery in runtime images", async () => {
281305
const dockerfile = collapseDockerContinuations(await readFile(dockerfilePath, "utf8"));
282306
expect(dockerfile).toContain(`ARG OPENCLAW_BUNDLED_PLUGIN_DIR=${BUNDLED_PLUGIN_ROOT_DIR}`);

0 commit comments

Comments
 (0)