diff --git a/scripts/docker/setup.sh b/scripts/docker/setup.sh index 5a145c7918d..276c9376581 100755 --- a/scripts/docker/setup.sh +++ b/scripts/docker/setup.sh @@ -3,6 +3,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" source "$ROOT_DIR/scripts/lib/docker-build.sh" +source "$ROOT_DIR/scripts/lib/host-timeout.sh" COMPOSE_FILE="$ROOT_DIR/docker-compose.yml" EXTRA_COMPOSE_FILE="$ROOT_DIR/docker-compose.extra.yml" IMAGE_NAME="${OPENCLAW_IMAGE:-openclaw:local}" @@ -52,15 +53,7 @@ run_docker_build() { run_docker_pull() { local image="$1" - if command -v timeout >/dev/null 2>&1; then - if timeout --kill-after=1s 1s true >/dev/null 2>&1; then - timeout --kill-after=30s "$DOCKER_PULL_TIMEOUT" docker pull "$image" - else - timeout "$DOCKER_PULL_TIMEOUT" docker pull "$image" - fi - return - fi - docker pull "$image" + openclaw_host_timeout_cmd "$DOCKER_PULL_TIMEOUT" docker pull "$image" } require_local_docker_image() { diff --git a/scripts/lib/host-timeout.sh b/scripts/lib/host-timeout.sh new file mode 100644 index 00000000000..cd7e35335b5 --- /dev/null +++ b/scripts/lib/host-timeout.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +openclaw_host_timeout_bin() { + if command -v timeout >/dev/null 2>&1; then + printf '%s\n' timeout + elif command -v gtimeout >/dev/null 2>&1; then + printf '%s\n' gtimeout + else + return 1 + fi +} + +openclaw_host_timeout_cmd() { + local timeout_value="$1" + shift + local timeout_bin + if ! timeout_bin="$(openclaw_host_timeout_bin)"; then + "$@" + return + fi + if "$timeout_bin" --kill-after=1s 1s true >/dev/null 2>&1; then + "$timeout_bin" --kill-after=30s "$timeout_value" "$@" + else + "$timeout_bin" "$timeout_value" "$@" + fi +} diff --git a/scripts/podman/setup.sh b/scripts/podman/setup.sh index 151147223bb..e47e7101ecb 100755 --- a/scripts/podman/setup.sh +++ b/scripts/podman/setup.sh @@ -17,6 +17,7 @@ set -euo pipefail REPO_PATH="${OPENCLAW_REPO_PATH:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +source "$REPO_PATH/scripts/lib/host-timeout.sh" RUN_SCRIPT_SRC="$REPO_PATH/scripts/run-openclaw-podman.sh" QUADLET_TEMPLATE="$REPO_PATH/scripts/podman/openclaw.container.in" OPENCLAW_USER="$(id -un)" @@ -47,27 +48,11 @@ fail() { run_podman_pull() { local image="$1" - if command -v timeout >/dev/null 2>&1; then - if timeout --kill-after=1s 1s true >/dev/null 2>&1; then - timeout --kill-after=30s "$PODMAN_PULL_TIMEOUT" podman pull "$image" - else - timeout "$PODMAN_PULL_TIMEOUT" podman pull "$image" - fi - return - fi - podman pull "$image" + openclaw_host_timeout_cmd "$PODMAN_PULL_TIMEOUT" podman pull "$image" } run_podman_build() { - if command -v timeout >/dev/null 2>&1; then - if timeout --kill-after=1s 1s true >/dev/null 2>&1; then - timeout --kill-after=30s "$PODMAN_BUILD_TIMEOUT" podman build "$@" - else - timeout "$PODMAN_BUILD_TIMEOUT" podman build "$@" - fi - return - fi - podman build "$@" + openclaw_host_timeout_cmd "$PODMAN_BUILD_TIMEOUT" podman build "$@" } validate_single_line_value() { diff --git a/scripts/run-openclaw-podman.sh b/scripts/run-openclaw-podman.sh index 375d59e4200..29390f749d7 100755 --- a/scripts/run-openclaw-podman.sh +++ b/scripts/run-openclaw-podman.sh @@ -14,6 +14,9 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib/host-timeout.sh +source "$SCRIPT_DIR/lib/host-timeout.sh" PLATFORM_NAME="$(uname -s 2>/dev/null || echo unknown)" resolve_user_home() { @@ -37,15 +40,7 @@ fail() { } run_podman_detached() { - if command -v timeout >/dev/null 2>&1; then - if timeout --kill-after=1s 1s true >/dev/null 2>&1; then - timeout --kill-after=30s "$PODMAN_RUN_TIMEOUT" podman run "$@" - else - timeout "$PODMAN_RUN_TIMEOUT" podman run "$@" - fi - return - fi - podman run "$@" + openclaw_host_timeout_cmd "$PODMAN_RUN_TIMEOUT" podman run "$@" } validate_single_line_value() { diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts index b5abaaff434..423f04061ff 100644 --- a/test/scripts/test-install-sh-docker.test.ts +++ b/test/scripts/test-install-sh-docker.test.ts @@ -10,6 +10,7 @@ const SCRIPT_PATH = "scripts/test-install-sh-docker.sh"; const INSTALL_E2E_DOCKER_PATH = "scripts/test-install-sh-e2e-docker.sh"; const INSTALL_E2E_RUNNER_PATH = "scripts/docker/install-sh-e2e/run.sh"; const DOCKER_SETUP_PATH = "scripts/docker/setup.sh"; +const HOST_TIMEOUT_PATH = "scripts/lib/host-timeout.sh"; const PODMAN_SETUP_PATH = "scripts/podman/setup.sh"; const PODMAN_RUN_PATH = "scripts/run-openclaw-podman.sh"; const SMOKE_RUNNER_PATH = "scripts/docker/install-sh-smoke/run.sh"; @@ -323,14 +324,14 @@ describe("test-install-sh-docker", () => { it("bounds Docker setup image pulls", () => { const script = readFileSync(DOCKER_SETUP_PATH, "utf8"); + const timeoutHelper = readFileSync(HOST_TIMEOUT_PATH, "utf8"); + expect(script).toContain('source "$ROOT_DIR/scripts/lib/host-timeout.sh"'); expect(script).toContain('DOCKER_PULL_TIMEOUT="${OPENCLAW_DOCKER_SETUP_PULL_TIMEOUT:-600s}"'); expect(script).toContain("run_docker_pull()"); - expect(script).toContain("timeout --kill-after=1s 1s true"); - expect(script).toContain( - 'timeout --kill-after=30s "$DOCKER_PULL_TIMEOUT" docker pull "$image"', - ); - expect(script).toContain('timeout "$DOCKER_PULL_TIMEOUT" docker pull "$image"'); + expect(script).toContain('openclaw_host_timeout_cmd "$DOCKER_PULL_TIMEOUT" docker pull "$image"'); + expect(timeoutHelper).toContain("elif command -v gtimeout >/dev/null 2>&1; then"); + expect(timeoutHelper).toContain('"$timeout_bin" --kill-after=30s "$timeout_value" "$@"'); expect(script).toContain('run_docker_pull "$IMAGE_NAME"'); expect(script).not.toContain('docker pull "$IMAGE_NAME"'); }); @@ -338,13 +339,12 @@ describe("test-install-sh-docker", () => { it("bounds Podman setup image pulls", () => { const script = readFileSync(PODMAN_SETUP_PATH, "utf8"); + expect(script).toContain('source "$REPO_PATH/scripts/lib/host-timeout.sh"'); expect(script).toContain('PODMAN_PULL_TIMEOUT="${OPENCLAW_PODMAN_SETUP_PULL_TIMEOUT:-600s}"'); expect(script).toContain("run_podman_pull()"); - expect(script).toContain("timeout --kill-after=1s 1s true"); expect(script).toContain( - 'timeout --kill-after=30s "$PODMAN_PULL_TIMEOUT" podman pull "$image"', + 'openclaw_host_timeout_cmd "$PODMAN_PULL_TIMEOUT" podman pull "$image"', ); - expect(script).toContain('timeout "$PODMAN_PULL_TIMEOUT" podman pull "$image"'); expect(script).toContain('run_podman_pull "$OPENCLAW_IMAGE"'); expect(script).not.toContain('podman pull "$OPENCLAW_IMAGE"'); }); @@ -356,9 +356,7 @@ describe("test-install-sh-docker", () => { 'PODMAN_BUILD_TIMEOUT="${OPENCLAW_PODMAN_SETUP_BUILD_TIMEOUT:-1800s}"', ); expect(script).toContain("run_podman_build()"); - expect(script).toContain("timeout --kill-after=1s 1s true"); - expect(script).toContain('timeout --kill-after=30s "$PODMAN_BUILD_TIMEOUT" podman build "$@"'); - expect(script).toContain('timeout "$PODMAN_BUILD_TIMEOUT" podman build "$@"'); + expect(script).toContain('openclaw_host_timeout_cmd "$PODMAN_BUILD_TIMEOUT" podman build "$@"'); expect(script).toContain('run_podman_build -t "$OPENCLAW_IMAGE"'); expect(script).not.toContain('podman build -t "$OPENCLAW_IMAGE"'); }); @@ -368,10 +366,9 @@ describe("test-install-sh-docker", () => { expect(script).toContain('PODMAN_RUN_TIMEOUT="${OPENCLAW_PODMAN_RUN_TIMEOUT:-600s}"'); expect(script).toContain("OPENCLAW_PODMAN_RUN_TIMEOUT|OPENCLAW_PODMAN_GATEWAY_HOST_PORT"); + expect(script).toContain('source "$SCRIPT_DIR/lib/host-timeout.sh"'); expect(script).toContain("run_podman_detached()"); - expect(script).toContain("timeout --kill-after=1s 1s true"); - expect(script).toContain('timeout --kill-after=30s "$PODMAN_RUN_TIMEOUT" podman run "$@"'); - expect(script).toContain('timeout "$PODMAN_RUN_TIMEOUT" podman run "$@"'); + expect(script).toContain('openclaw_host_timeout_cmd "$PODMAN_RUN_TIMEOUT" podman run "$@"'); expect(script).toContain('podman run --pull="$PODMAN_PULL" --rm -it \\'); expect(script).toContain('run_podman_detached --pull="$PODMAN_PULL" -d --replace \\'); expect(script).not.toContain('podman run --pull="$PODMAN_PULL" -d --replace \\');