From 66a4205c57068074be3ea4f5a941bc9b5a3de7d3 Mon Sep 17 00:00:00 2001
From: Alix-007
Date: Thu, 16 Jul 2026 20:26:00 +0800
Subject: [PATCH] fix(e2e): bound official installer download (#108937)
* fix(e2e): bound official installer download
* test(e2e): cover downloaded installer execution
* fix(e2e): harden installer download proof
Co-authored-by: Alix-007
* test(e2e): type installer fixture environment
---------
Co-authored-by: Peter Steinberger
---
scripts/docker/install-sh-e2e/run.sh | 16 ++-
test/scripts/docker-build-helper.test.ts | 4 +-
test/scripts/test-install-sh-docker.test.ts | 115 ++++++++++++++++++++
3 files changed, 128 insertions(+), 7 deletions(-)
diff --git a/scripts/docker/install-sh-e2e/run.sh b/scripts/docker/install-sh-e2e/run.sh
index b0b2b1e0aa3b..19a2e472b30c 100755
--- a/scripts/docker/install-sh-e2e/run.sh
+++ b/scripts/docker/install-sh-e2e/run.sh
@@ -150,15 +150,21 @@ preinstall_previous_version() {
fi
}
-run_official_installer() {
+run_official_installer() (
+ local installer
+ # time_phase disables errexit, so setup and download failures must return explicitly.
+ installer="$(mktemp)" || return
+ trap 'rm -f "$installer"' EXIT
+
+ curl -fsSL --connect-timeout 10 --max-time 120 "$INSTALL_URL" -o "$installer" || return
if [[ "$INSTALL_TAG" == "beta" ]]; then
- curl -fsSL "$INSTALL_URL" | OPENCLAW_BETA=1 bash
+ OPENCLAW_BETA=1 bash "$installer"
elif [[ "$INSTALL_TAG" != "latest" ]]; then
- curl -fsSL "$INSTALL_URL" | OPENCLAW_VERSION="$INSTALL_TAG" bash
+ OPENCLAW_VERSION="$INSTALL_TAG" bash "$installer"
else
- curl -fsSL "$INSTALL_URL" | bash
+ bash "$installer"
fi
-}
+)
verify_installed_version() {
INSTALLED_VERSION="$(openclaw --version 2>/dev/null | head -n 1 | tr -d '\r')"
diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts
index 4c1e8cfd36c7..4516bc6c3524 100644
--- a/test/scripts/docker-build-helper.test.ts
+++ b/test/scripts/docker-build-helper.test.ts
@@ -4348,8 +4348,8 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}"
it("passes installer tag env to bash, not curl", () => {
const runner = readFileSync(INSTALL_E2E_RUNNER_PATH, "utf8");
- expect(runner).toContain('curl -fsSL "$INSTALL_URL" | OPENCLAW_BETA=1 bash');
- expect(runner).toContain('curl -fsSL "$INSTALL_URL" | OPENCLAW_VERSION="$INSTALL_TAG" bash');
+ expect(runner).toContain('OPENCLAW_BETA=1 bash "$installer"');
+ expect(runner).toContain('OPENCLAW_VERSION="$INSTALL_TAG" bash "$installer"');
expect(runner).not.toContain('OPENCLAW_BETA=1 curl -fsSL "$INSTALL_URL" | bash');
expect(runner).not.toContain(
'OPENCLAW_VERSION="$INSTALL_TAG" curl -fsSL "$INSTALL_URL" | bash',
diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts
index 91994e8a474e..1b964576bece 100644
--- a/test/scripts/test-install-sh-docker.test.ts
+++ b/test/scripts/test-install-sh-docker.test.ts
@@ -59,6 +59,87 @@ function extractNonrootNodePreflight(): string {
return expectDefined(match[1], "non-root smoke Node preflight capture");
}
+function extractInstallE2eInstallerFunction(): string {
+ const script = readFileSync(INSTALL_E2E_RUNNER_PATH, "utf8");
+ const startMarker = "run_official_installer() (\n";
+ const endMarker = "\n\nverify_installed_version()";
+ const start = script.indexOf(startMarker);
+ const end = script.indexOf(endMarker, start + startMarker.length);
+ if (start < 0 || end <= start) {
+ throw new Error("install E2E installer function was not found");
+ }
+ return script.slice(start, end);
+}
+
+function runInstallE2eInstallerFixture(params: {
+ curlExitCode?: number;
+ installTag: string;
+ installerBody: string;
+}) {
+ const root = tempDirs.make("openclaw-install-e2e-download-");
+ const binDir = join(root, "bin");
+ const curlPath = join(binDir, "curl");
+ const curlArgsPath = join(root, "curl-args.txt");
+ const installerSourcePath = join(root, "installer-source.sh");
+ const markerPath = join(root, "installer-marker.txt");
+ const outputPathCapture = join(root, "curl-output-path.txt");
+ mkdirSync(binDir, { recursive: true });
+ writeFileSync(installerSourcePath, params.installerBody);
+ writeFileSync(
+ curlPath,
+ [
+ "#!/bin/sh",
+ "set -eu",
+ 'printf \'%s\\n\' "$*" >"$CURL_ARGS_PATH"',
+ 'output=""',
+ 'while [ "$#" -gt 0 ]; do',
+ ' if [ "$1" = "-o" ]; then',
+ " shift",
+ ' output="$1"',
+ " fi",
+ " shift",
+ "done",
+ 'cp "$FAKE_INSTALLER_SOURCE" "$output"',
+ 'printf "%s" "$output" >"$OUTPUT_PATH_CAPTURE"',
+ 'exit "$FAKE_CURL_EXIT"',
+ "",
+ ].join("\n"),
+ { mode: 0o755 },
+ );
+
+ const env: NodeJS.ProcessEnv = {
+ ...process.env,
+ CURL_ARGS_PATH: curlArgsPath,
+ FAKE_CURL_EXIT: String(params.curlExitCode ?? 0),
+ FAKE_INSTALLER_SOURCE: installerSourcePath,
+ INSTALL_MARKER: markerPath,
+ OUTPUT_PATH_CAPTURE: outputPathCapture,
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
+ };
+ delete env.OPENCLAW_BETA;
+ delete env.OPENCLAW_VERSION;
+
+ const result = spawnSync(
+ "/bin/bash",
+ [
+ "-c",
+ [
+ "set -u",
+ extractInstallE2eInstallerFunction(),
+ 'INSTALL_URL="https://installer.example.test/install.sh"',
+ `INSTALL_TAG=${JSON.stringify(params.installTag)}`,
+ "run_official_installer",
+ ].join("\n"),
+ ],
+ {
+ encoding: "utf8",
+ env,
+ },
+ );
+
+ return { curlArgsPath, markerPath, outputPathCapture, result };
+}
+
function runNonrootNodePreflight(
version: string,
options: { sqlite?: boolean; sqliteVersion?: string } = {},
@@ -960,6 +1041,40 @@ printf 'status=%s\\n' "$status"
});
describe("install-sh E2E runner", () => {
+ it("does not execute a partial installer after a bounded download fails", () => {
+ const fixture = runInstallE2eInstallerFixture({
+ curlExitCode: 28,
+ installerBody: 'touch "$INSTALL_MARKER"\n',
+ installTag: "latest",
+ });
+
+ expect(fixture.result.status).toBe(28);
+ expect(readFileSync(fixture.curlArgsPath, "utf8")).toContain(
+ "-fsSL --connect-timeout 10 --max-time 120 https://installer.example.test/install.sh -o",
+ );
+ expect(existsSync(fixture.markerPath)).toBe(false);
+ expect(existsSync(readFileSync(fixture.outputPathCapture, "utf8"))).toBe(false);
+ });
+
+ it.each([
+ ["latest", "|"],
+ ["beta", "1|"],
+ ["2026.7.1", "|2026.7.1"],
+ ])(
+ "executes a complete %s installer with the expected tag environment",
+ (installTag, expected) => {
+ const fixture = runInstallE2eInstallerFixture({
+ installTag,
+ installerBody:
+ 'printf "%s|%s" "${OPENCLAW_BETA-}" "${OPENCLAW_VERSION-}" >"$INSTALL_MARKER"\n',
+ });
+
+ expect(fixture.result.status, fixture.result.stderr).toBe(0);
+ expect(readFileSync(fixture.markerPath, "utf8")).toBe(expected);
+ expect(existsSync(readFileSync(fixture.outputPathCapture, "utf8"))).toBe(false);
+ },
+ );
+
it("normalizes Docker wrapper timing and toggle knobs before forwarding", () => {
const wrapper = readFileSync(INSTALL_E2E_DOCKER_PATH, "utf8");