diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index 64b68971cac..fa325eb8e88 100755 --- a/scripts/docker/install-sh-smoke/run.sh +++ b/scripts/docker/install-sh-smoke/run.sh @@ -13,6 +13,10 @@ UPDATE_BASELINE_VERSION="${OPENCLAW_INSTALL_UPDATE_BASELINE:-latest}" UPDATE_BASELINE_TAG_URL="${OPENCLAW_INSTALL_UPDATE_BASELINE_TAG_URL:-}" UPDATE_EXPECT_VERSION="${OPENCLAW_INSTALL_UPDATE_EXPECT_VERSION:-}" UPDATE_TAG_URL="${OPENCLAW_INSTALL_UPDATE_TAG_URL:-}" +FRESHNESS_VERSION="${OPENCLAW_INSTALL_FRESHNESS_VERSION:-latest}" +# npm min-release-age is days; 10000 keeps the control failure independent of normal release cadence. +FRESHNESS_MIN_RELEASE_AGE="${OPENCLAW_INSTALL_FRESHNESS_MIN_RELEASE_AGE:-10000}" +FRESHNESS_NPM_VERSION="${OPENCLAW_INSTALL_FRESHNESS_NPM_VERSION:-11.14.1}" HEARTBEAT_INTERVAL="${OPENCLAW_INSTALL_SMOKE_HEARTBEAT_INTERVAL:-60}" INSTALL_COMMAND_TIMEOUT="${OPENCLAW_INSTALL_SMOKE_COMMAND_TIMEOUT:-900}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -391,6 +395,75 @@ run_npm_global_smoke() { echo "OK" } +run_freshness_smoke() { + local freshness_spec="${PACKAGE_NAME}@${FRESHNESS_VERSION}" + local expected_version + local current_npm_version + local policy_home + local plain_stdout_file + local plain_stderr_file + local plain_status + policy_home="$(mktemp -d)" + plain_stdout_file="$(mktemp)" + plain_stderr_file="$(mktemp)" + printf "min-release-age=%s\n" "$FRESHNESS_MIN_RELEASE_AGE" >"${policy_home}/.npmrc" + + current_npm_version="$(npm --version 2>/dev/null || true)" + if [[ "$current_npm_version" != "$FRESHNESS_NPM_VERSION" ]]; then + echo "==> Install npm with min-release-age support: npm@$FRESHNESS_NPM_VERSION" + npm_install_global "install npm freshness-capable release" "npm@${FRESHNESS_NPM_VERSION}" + fi + + expected_version="$(quiet_npm view "$freshness_spec" version 2>/dev/null || true)" + if [[ -z "$expected_version" ]]; then + echo "ERROR: failed to resolve $freshness_spec" >&2 + return 1 + fi + + echo "package=$PACKAGE_NAME version=$FRESHNESS_VERSION resolved=$expected_version npm=$(npm --version) min_release_age=$FRESHNESS_MIN_RELEASE_AGE" + echo "==> Verify user npm freshness policy blocks plain npm install" + set +e + HOME="$policy_home" NPM_CONFIG_USERCONFIG="${policy_home}/.npmrc" \ + timeout --foreground "${INSTALL_COMMAND_TIMEOUT}s" \ + npm \ + --loglevel=error \ + --logs-max=0 \ + --no-update-notifier \ + --no-fund \ + --no-audit \ + --no-progress \ + install -g "$freshness_spec" \ + >"$plain_stdout_file" 2>"$plain_stderr_file" + plain_status=$? + set -e + if [[ "$plain_status" -eq 0 ]]; then + echo "ERROR: plain npm install unexpectedly succeeded under min-release-age policy" >&2 + return 1 + fi + if ! grep -Eiq "No matching version|No versions available|ETARGET|ENOVERSIONS|notarget|min-release-age|minimum release age|before" \ + "$plain_stdout_file" "$plain_stderr_file"; then + echo "ERROR: plain npm install failed without expected freshness evidence" >&2 + cat "$plain_stdout_file" + cat "$plain_stderr_file" >&2 + return 1 + fi + + echo "==> Run installer with same npm freshness policy" + env \ + HOME="$policy_home" \ + NPM_CONFIG_USERCONFIG="${policy_home}/.npmrc" \ + OPENCLAW_NO_ONBOARD=1 \ + OPENCLAW_NO_PROMPT=1 \ + bash -c 'curl -fsSL "$1" | bash -s -- --install-method npm --version "$2" --no-prompt --no-onboard' \ + _ "$INSTALL_URL" "$FRESHNESS_VERSION" + + echo "==> Verify installed version" + print_install_audit "freshness install" + verify_installed_cli "$PACKAGE_NAME" "$expected_version" + + echo "OK" +} + case "$SMOKE_MODE" in install) run_install_smoke @@ -401,6 +474,9 @@ case "$SMOKE_MODE" in npm-global) run_npm_global_smoke ;; + freshness) + run_freshness_smoke + ;; *) echo "ERROR: unsupported OPENCLAW_INSTALL_SMOKE_MODE=$SMOKE_MODE" >&2 exit 1 diff --git a/scripts/test-install-sh-docker.sh b/scripts/test-install-sh-docker.sh index f82bb813594..cc01453a354 100755 --- a/scripts/test-install-sh-docker.sh +++ b/scripts/test-install-sh-docker.sh @@ -149,6 +149,11 @@ SKIP_SMOKE_IMAGE_BUILD="${OPENCLAW_INSTALL_SMOKE_SKIP_IMAGE_BUILD:-0}" SKIP_NONROOT_IMAGE_BUILD="${OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD:-0}" SKIP_UPDATE="${OPENCLAW_INSTALL_SMOKE_SKIP_UPDATE:-0}" SKIP_NPM_GLOBAL="${OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL:-0}" +SKIP_FRESHNESS="${OPENCLAW_INSTALL_SMOKE_SKIP_FRESHNESS:-0}" +FRESHNESS_INSTALL_URL="${OPENCLAW_INSTALL_SMOKE_FRESHNESS_INSTALL_URL:-file:///tmp/openclaw-install.sh}" +# npm min-release-age is days; 10000 keeps the control failure independent of normal release cadence. +FRESHNESS_MIN_RELEASE_AGE="${OPENCLAW_INSTALL_FRESHNESS_MIN_RELEASE_AGE:-10000}" +FRESHNESS_NPM_VERSION="${OPENCLAW_INSTALL_FRESHNESS_NPM_VERSION:-11.14.1}" UPDATE_BASELINE_VERSION="${OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE:-latest}" UPDATE_PACKAGE_SPEC="${OPENCLAW_INSTALL_SMOKE_UPDATE_PACKAGE_SPEC:-}" UPDATE_DIST_IMAGE="${OPENCLAW_INSTALL_SMOKE_UPDATE_DIST_IMAGE:-}" @@ -169,6 +174,7 @@ UPDATE_TAG_URL="" UPDATE_DOCKER_HOST_ARGS=() NPM_CACHE_DIR="${OPENCLAW_INSTALL_SMOKE_NPM_CACHE_DIR:-}" NPM_CACHE_OWNED=0 +NPM_CACHE_PREPARED=0 NPM_CACHE_DOCKER_ARGS=() remove_owned_npm_cache() { @@ -334,6 +340,9 @@ prepare_update_host_access() { } prepare_npm_cache() { + if [[ "$NPM_CACHE_PREPARED" == "1" ]]; then + return + fi if [[ -z "$NPM_CACHE_DIR" ]]; then NPM_CACHE_DIR="$(mktemp -d)" NPM_CACHE_OWNED=1 @@ -345,6 +354,7 @@ prepare_npm_cache() { -e npm_config_cache=/npm-cache -e NPM_CONFIG_CACHE=/npm-cache ) + NPM_CACHE_PREPARED=1 } start_update_server() { @@ -447,6 +457,27 @@ else fi fi +if [[ "$SKIP_FRESHNESS" == "1" ]]; then + echo "==> Skip installer npm freshness smoke (OPENCLAW_INSTALL_SMOKE_SKIP_FRESHNESS=1)" +else + prepare_npm_cache + echo "==> Run installer npm freshness smoke" + docker run --rm -t \ + --platform "$SMOKE_PLATFORM" \ + "${NPM_CACHE_DOCKER_ARGS[@]}" \ + -v "$ROOT_DIR/scripts/install.sh:/tmp/openclaw-install.sh:ro" \ + -e OPENCLAW_INSTALL_URL="$FRESHNESS_INSTALL_URL" \ + -e OPENCLAW_INSTALL_PACKAGE="$PACKAGE_NAME" \ + -e OPENCLAW_INSTALL_SMOKE_MODE=freshness \ + -e OPENCLAW_INSTALL_FRESHNESS_VERSION="${OPENCLAW_INSTALL_FRESHNESS_VERSION:-latest}" \ + -e OPENCLAW_INSTALL_FRESHNESS_MIN_RELEASE_AGE="$FRESHNESS_MIN_RELEASE_AGE" \ + -e OPENCLAW_INSTALL_FRESHNESS_NPM_VERSION="$FRESHNESS_NPM_VERSION" \ + -e OPENCLAW_NO_ONBOARD=1 \ + -e OPENCLAW_NO_PROMPT=1 \ + -e DEBIAN_FRONTEND=noninteractive \ + "$SMOKE_IMAGE" +fi + LATEST_VERSION="${LATEST_VERSION:-}" if [[ "$SKIP_NONROOT" == "1" ]]; then