diff --git a/.github/workflows/build-node-musl.yml b/.github/workflows/build-node-musl.yml index 935f6c7..c059777 100644 --- a/.github/workflows/build-node-musl.yml +++ b/.github/workflows/build-node-musl.yml @@ -62,6 +62,27 @@ jobs: echo "=== Tarball contents (first 20 entries) ===" tar tJf dist/*.tar.xz | head -20 + - name: Verify relocatable tarball + run: | + docker run --rm --platform linux/arm64 \ + -v "$PWD/dist:/dist:ro" \ + alpine:3.21 sh -euxc ' + apk add --no-cache xz icu-data-full tar + TARBALL=$(ls /dist/*.tar.xz | head -1) + verify_prefix() { + prefix="$1" + rm -rf "$prefix" + mkdir -p "$prefix" + tar -xJf "$TARBALL" --strip-components=1 -C "$prefix" + "$prefix/bin/node" --version + exec_path=$("$prefix/bin/node" -e "process.stdout.write(process.execPath)") + [ "$exec_path" = "$prefix/bin/node" ] + NODE_ICU_DATA="$prefix/share/icu" "$prefix/bin/npm" --version >/dev/null + } + verify_prefix /opt/openclaw/node + verify_prefix /tmp/custom-openclaw-root/openclaw/node + ' + - name: Upload artifact uses: actions/upload-artifact@v4 with: @@ -115,6 +136,27 @@ jobs: echo "=== Tarball contents (first 20 entries) ===" tar tJf dist/*.tar.xz | head -20 + - name: Verify relocatable tarball + run: | + docker run --rm --platform linux/arm64 \ + -v "$PWD/dist:/dist:ro" \ + alpine:3.21 sh -euxc ' + apk add --no-cache xz icu-data-full tar + TARBALL=$(ls /dist/*.tar.xz | head -1) + verify_prefix() { + prefix="$1" + rm -rf "$prefix" + mkdir -p "$prefix" + tar -xJf "$TARBALL" --strip-components=1 -C "$prefix" + "$prefix/bin/node" --version + exec_path=$("$prefix/bin/node" -e "process.stdout.write(process.execPath)") + [ "$exec_path" = "$prefix/bin/node" ] + NODE_ICU_DATA="$prefix/share/icu" "$prefix/bin/npm" --version >/dev/null + } + verify_prefix /opt/openclaw/node + verify_prefix /tmp/custom-openclaw-root/openclaw/node + ' + - name: Upload artifact uses: actions/upload-artifact@v4 with: diff --git a/Makefile b/Makefile index a6275b0..921c665 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,7 @@ define Package/$(PKG_NAME)/install $(INSTALL_BIN) ./root/usr/bin/openclaw-env $(1)/usr/bin/openclaw-env $(INSTALL_DIR) $(1)/usr/libexec $(INSTALL_BIN) ./root/usr/libexec/openclaw-paths.sh $(1)/usr/libexec/openclaw-paths.sh + $(INSTALL_BIN) ./root/usr/libexec/openclaw-node.sh $(1)/usr/libexec/openclaw-node.sh $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller $(INSTALL_DATA) ./luasrc/controller/openclaw.lua $(1)/usr/lib/lua/luci/controller/openclaw.lua $(INSTALL_DIR) $(1)/usr/lib/lua/openclaw diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env index 0189c84..c1f776d 100755 --- a/root/usr/bin/openclaw-env +++ b/root/usr/bin/openclaw-env @@ -12,10 +12,11 @@ set -e . /usr/libexec/openclaw-paths.sh +. /usr/libexec/openclaw-node.sh -# ── Node.js 版本策略 (双版本兼容) ── +# ── Node.js 版本策略 ── # V2: 当前推荐版本,用于 OpenClaw v2026.3.11+ (要求 >= 22.16.0) -# V1: 旧版兼容,用于 OpenClaw v2026.3.8 及更早版本 +# V1: 保留给显式指定旧版环境时使用,不再作为 V2 的自动回退 NODE_VERSION_V2="22.16.0" NODE_VERSION_V1="22.15.1" # 默认使用 V2 版本 (可通过 NODE_VERSION 环境变量覆盖) @@ -148,23 +149,23 @@ download_node() { # 如果已存在且版本兼容, 跳过 if [ -x "$NODE_BIN" ]; then local current_ver - current_ver=$("$NODE_BIN" --version 2>/dev/null | sed 's/^v//') - if [ "$current_ver" = "$node_ver" ]; then + current_ver=$(oc_read_node_version "$NODE_BIN" || true) + if [ -n "$current_ver" ] && [ "$current_ver" = "$node_ver" ]; then log_info "Node.js v${node_ver} 已安装, 跳过下载" return 0 fi - # ARM64 musl 使用 Alpine 打包,版本号可能不完全匹配 - # 只要主版本号相同即认为兼容 (如 22.15.1 vs 22.15.0) - local cur_major=$(echo "$current_ver" | cut -d. -f1) - local want_major=$(echo "$node_ver" | cut -d. -f1) - if [ "$cur_major" = "$want_major" ]; then - log_info "Node.js v${current_ver} 已安装 (兼容 v${node_ver}), 跳过下载" + if [ -n "$current_ver" ] && oc_node_version_ge "$current_ver" "$node_ver"; then + log_info "Node.js v${current_ver} 已安装 (满足 >= v${node_ver}), 跳过下载" return 0 fi - log_warn "当前 Node.js v${current_ver}, 将更新到 v${node_ver}" + if [ -n "$current_ver" ]; then + log_warn "当前 Node.js v${current_ver} 低于要求 v${node_ver}, 将更新" + else + log_warn "检测到现有 Node.js 文件但无法运行,将重新安装" + fi fi - # ── 构建下载 URL 列表 (按优先级排列,支持双版本回退) ── + # ── 构建下载 URL 列表 (按优先级排列) ── local mirror_list="" local musl_tarball="node-v${node_ver}-${node_arch}-musl.tar.xz" local glibc_tarball="node-v${node_ver}-${node_arch}.tar.xz" @@ -179,11 +180,6 @@ download_node() { mirror_list="${NODE_SELF_HOST}/${musl_tarball}" # 2) unofficial-builds (留作将来可能支持) mirror_list="$mirror_list ${NODE_MUSL_MIRROR}/v${node_ver}/${musl_tarball}" - # 3) 回退到 V1 版本 (兼容旧环境,仅当请求 V2 时) - if [ "$node_ver" = "$NODE_VERSION_V2" ]; then - local v1_tarball="node-v${NODE_VERSION_V1}-${node_arch}-musl.tar.xz" - mirror_list="$mirror_list ${NODE_SELF_HOST}/${v1_tarball}" - fi else # x64 musl: unofficial-builds 提供 # 1) unofficial-builds @@ -262,14 +258,18 @@ download_node() { rm -f "$tmp_file" # 验证 - if [ -x "$NODE_BIN" ]; then - local installed_ver - installed_ver=$("$NODE_BIN" --version 2>/dev/null || echo "unknown") + local installed_ver + installed_ver=$(oc_read_node_version "$NODE_BIN" || true) + if [ -n "$installed_ver" ] && oc_node_version_ge "$installed_ver" "$node_ver"; then log_info "Node.js ${installed_ver} 安装成功" + return 0 + fi + if [ -n "$installed_ver" ]; then + log_error "Node.js 版本过低: v${installed_ver} < v${node_ver}" else log_error "Node.js 安装验证失败" - exit 1 fi + exit 1 } install_pnpm() { @@ -410,9 +410,11 @@ init_openclaw() { do_setup() { local node_ver="$NODE_VERSION" + local installed_node_ver="" + installed_node_ver=$(oc_read_node_version "$NODE_BIN" || true) # 检查是否已安装 - if [ -x "$NODE_BIN" ] && [ -n "$(find_oc_entry)" ]; then + if [ -n "$installed_node_ver" ] && [ -n "$(find_oc_entry)" ]; then local oc_ver="" local oc_entry="$(find_oc_entry)" local oc_pkg_dir="$(dirname "$oc_entry")" @@ -422,7 +424,7 @@ do_setup() { echo "║ ⚠️ OpenClaw 运行环境已安装,无需重复安装 ║" echo "╚══════════════════════════════════════════════════════════════╝" echo "" - echo " Node.js: $($NODE_BIN --version 2>/dev/null)" + echo " Node.js: v${installed_node_ver}" [ -n "$oc_ver" ] && echo " OpenClaw: v${oc_ver}" echo "" echo " 如需升级,请使用: openclaw-env upgrade" @@ -465,8 +467,10 @@ do_check() { echo "" # Node.js - if [ -x "$NODE_BIN" ]; then - echo " Node.js: $($NODE_BIN --version 2>/dev/null)" + local installed_node_ver="" + installed_node_ver=$(oc_read_node_version "$NODE_BIN" || true) + if [ -n "$installed_node_ver" ]; then + echo " Node.js: v${installed_node_ver}" else echo " Node.js: 未安装" fi @@ -511,7 +515,9 @@ do_upgrade() { echo "╚══════════════════════════════════════════════════════════════╝" echo "" - if [ ! -x "$NODE_BIN" ]; then + local installed_node_ver="" + installed_node_ver=$(oc_read_node_version "$NODE_BIN" || true) + if [ -z "$installed_node_ver" ]; then log_error "Node.js 未安装, 请先运行: openclaw-env setup" exit 1 fi @@ -526,7 +532,7 @@ do_upgrade() { current_ver=$("$NODE_BIN" -e "try{console.log(require('$oc_pkg_dir/package.json').version)}catch(e){}" 2>/dev/null) || true fi - echo " Node.js: $($NODE_BIN --version 2>/dev/null)" + echo " Node.js: v${installed_node_ver}" [ -n "$current_ver" ] && echo " 当前版本: v${current_ver}" echo "" @@ -643,7 +649,9 @@ do_setup_offline() { fi # 检查是否已安装 - if [ -x "$NODE_BIN" ] && [ -n "$(find_oc_entry)" ]; then + local installed_node_ver="" + installed_node_ver=$(oc_read_node_version "$NODE_BIN" || true) + if [ -n "$installed_node_ver" ] && [ -n "$(find_oc_entry)" ]; then log_warn "OpenClaw 运行环境已安装" log_warn "如需重新离线安装,请先卸载现有环境" exit 0 @@ -680,8 +688,10 @@ do_setup_offline() { rm -rf "$tmp_extract" fi - if [ -x "$NODE_BIN" ]; then - log_info "Node.js $($NODE_BIN --version 2>/dev/null) 安装成功" + local offline_node_ver="" + offline_node_ver=$(oc_read_node_version "$NODE_BIN" || true) + if [ -n "$offline_node_ver" ]; then + log_info "Node.js v${offline_node_ver} 安装成功" else log_error "Node.js 安装失败" exit 1 diff --git a/root/usr/libexec/openclaw-node.sh b/root/usr/libexec/openclaw-node.sh new file mode 100644 index 0000000..3354240 --- /dev/null +++ b/root/usr/libexec/openclaw-node.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# Shared OpenClaw Node.js runtime/version helpers. + +oc_normalize_node_version() { + local version="${1:-}" + local old_ifs + + [ -n "$version" ] || return 1 + case "$version" in + v*) version="${version#v}" ;; + esac + case "$version" in + ''|*[!0-9.]*) return 1 ;; + esac + + old_ifs="$IFS" + IFS=. + set -- $version + IFS="$old_ifs" + + [ "$#" -eq 3 ] || return 1 + for part in "$1" "$2" "$3"; do + case "$part" in + ''|*[!0-9]*) return 1 ;; + esac + done + + printf '%s.%s.%s\n' "$1" "$2" "$3" +} + +oc_node_version_ge() { + local lhs rhs + local old_ifs + local lhs_major lhs_minor lhs_patch + local rhs_major rhs_minor rhs_patch + + lhs=$(oc_normalize_node_version "${1:-}") || return 1 + rhs=$(oc_normalize_node_version "${2:-}") || return 1 + + old_ifs="$IFS" + IFS=. + set -- $lhs + lhs_major="$1" + lhs_minor="$2" + lhs_patch="$3" + set -- $rhs + rhs_major="$1" + rhs_minor="$2" + rhs_patch="$3" + IFS="$old_ifs" + + [ "$lhs_major" -gt "$rhs_major" ] && return 0 + [ "$lhs_major" -lt "$rhs_major" ] && return 1 + [ "$lhs_minor" -gt "$rhs_minor" ] && return 0 + [ "$lhs_minor" -lt "$rhs_minor" ] && return 1 + [ "$lhs_patch" -ge "$rhs_patch" ] +} + +oc_read_node_version() { + local node_bin="${1:-}" + local version + + [ -n "$node_bin" ] || return 1 + [ -x "$node_bin" ] || return 1 + + version=$("$node_bin" --version 2>/dev/null) || return 1 + oc_normalize_node_version "$version" +} diff --git a/scripts/build-node-musl.sh b/scripts/build-node-musl.sh index 9e3fc3e..35bbffd 100755 --- a/scripts/build-node-musl.sh +++ b/scripts/build-node-musl.sh @@ -12,14 +12,16 @@ # 1. apk 模式: 使用 Alpine apk 安装 nodejs,版本受限于 Alpine 仓库 # 2. cross 模式: 从 Node.js 官方下载 glibc 版本,转换为 musl # 使用 patchelf 修改 node 二进制的 ELF interpreter 和 rpath, -# 使其直接使用打包的 musl 链接器和共享库,无需 LD_LIBRARY_PATH。 +# 使其使用系统 musl 动态链接器和相对 rpath,可从任意安装根目录直接运行。 # 这样 process.execPath 返回正确的 node 路径,子进程 fork 也能正常工作。 -# 安装路径固定为 /opt/openclaw/node (与 openclaw-env 一致)。 # ============================================================================ set -e -INSTALL_PREFIX="/opt/openclaw/node" BUILD_MODE="${BUILD_MODE:-apk}" +DEFAULT_VERIFY_PREFIX="/opt/openclaw/node" +CUSTOM_VERIFY_PREFIX="/tmp/custom-openclaw-root/openclaw/node" +SYSTEM_MUSL_LOADER="/lib/ld-musl-aarch64.so.1" +RELATIVE_RPATH='$ORIGIN/../lib' echo "=== Node.js ARM64 musl Build ===" echo " Target version: v${NODE_VER}" @@ -198,10 +200,10 @@ finalize_package() { # 用 patchelf 修改 node 二进制 echo "=== Patching ELF binary ===" - patchelf --set-interpreter "${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1" "${PKG_DIR}/bin/node" - patchelf --set-rpath "${INSTALL_PREFIX}/lib" "${PKG_DIR}/bin/node" - echo " interpreter: ${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1" - echo " rpath: ${INSTALL_PREFIX}/lib" + patchelf --set-interpreter "/lib/ld-musl-aarch64.so.1" "${PKG_DIR}/bin/node" + patchelf --set-rpath "$RELATIVE_RPATH" "${PKG_DIR}/bin/node" + echo " interpreter: ${SYSTEM_MUSL_LOADER}" + echo " rpath: ${RELATIVE_RPATH}" # 创建 node wrapper 脚本 cat > "${PKG_DIR}/bin/node-wrapper" << 'NODEWRAPPER' @@ -229,20 +231,27 @@ exec "${SELF_DIR}/node" "${SELF_DIR}/../lib/node_modules/npm/bin/npx-cli.js" "$@ NPXWRAPPER chmod +x "${PKG_DIR}/bin/npm" "${PKG_DIR}/bin/npx" - # 验证 - echo "=== Verification ===" - mkdir -p "${INSTALL_PREFIX}" - cp -a "${PKG_DIR}"/* "${INSTALL_PREFIX}/" - - # 设置库路径并测试 - export LD_LIBRARY_PATH="${INSTALL_PREFIX}/lib" - - "${INSTALL_PREFIX}/bin/node" --version - "${INSTALL_PREFIX}/bin/node" -e "console.log('execPath:', process.execPath)" - "${INSTALL_PREFIX}/bin/node" -e "console.log(process.arch, process.platform, process.versions.modules)" - NODE_ICU_DATA="${INSTALL_PREFIX}/share/icu" "${INSTALL_PREFIX}/bin/npm" --version 2>/dev/null || echo "npm needs ICU data" - - rm -rf "${INSTALL_PREFIX}" + verify_prefix() { + local prefix="$1" + local label="$2" + local exec_path="" + + echo "=== Verification (${label}) ===" + rm -rf "$prefix" 2>/dev/null || true + mkdir -p "$prefix" + cp -a "${PKG_DIR}"/* "${prefix}/" + + "${prefix}/bin/node" --version + exec_path=$("${prefix}/bin/node" -e "process.stdout.write(process.execPath)") + [ "$exec_path" = "${prefix}/bin/node" ] + "${prefix}/bin/node" -e "console.log(process.arch, process.platform, process.versions.modules)" + NODE_ICU_DATA="${prefix}/share/icu" "${prefix}/bin/npm" --version >/dev/null + + rm -rf "$prefix" 2>/dev/null || true + } + + verify_prefix "$DEFAULT_VERIFY_PREFIX" "default install root" + verify_prefix "$CUSTOM_VERIFY_PREFIX" "custom install root" # 打包 echo "=== Creating tarball ===" diff --git a/scripts/build_ipk.sh b/scripts/build_ipk.sh index cc1c452..2fc61d3 100755 --- a/scripts/build_ipk.sh +++ b/scripts/build_ipk.sh @@ -51,10 +51,20 @@ mkdir -p "$DATA_DIR/usr/bin" cp "$PKG_DIR/root/usr/bin/openclaw-env" "$DATA_DIR/usr/bin/" chmod +x "$DATA_DIR/usr/bin/openclaw-env" +# libexec helpers +mkdir -p "$DATA_DIR/usr/libexec" +cp "$PKG_DIR/root/usr/libexec/openclaw-paths.sh" "$DATA_DIR/usr/libexec/" +cp "$PKG_DIR/root/usr/libexec/openclaw-node.sh" "$DATA_DIR/usr/libexec/" +chmod +x "$DATA_DIR/usr/libexec/openclaw-paths.sh" "$DATA_DIR/usr/libexec/openclaw-node.sh" + # LuCI controller mkdir -p "$DATA_DIR/usr/lib/lua/luci/controller" cp "$PKG_DIR/luasrc/controller/openclaw.lua" "$DATA_DIR/usr/lib/lua/luci/controller/" +# Shared Lua helpers +mkdir -p "$DATA_DIR/usr/lib/lua/openclaw" +cp "$PKG_DIR/luasrc/openclaw/paths.lua" "$DATA_DIR/usr/lib/lua/openclaw/" + # LuCI CBI mkdir -p "$DATA_DIR/usr/lib/lua/luci/model/cbi/openclaw" cp "$PKG_DIR/luasrc/model/cbi/openclaw/"*.lua "$DATA_DIR/usr/lib/lua/luci/model/cbi/openclaw/" diff --git a/scripts/build_run.sh b/scripts/build_run.sh index 4ca6235..c91c169 100755 --- a/scripts/build_run.sh +++ b/scripts/build_run.sh @@ -54,10 +54,20 @@ install_files() { cp "$PKG_DIR/root/usr/bin/openclaw-env" "$dest/usr/bin/" chmod +x "$dest/usr/bin/openclaw-env" + # libexec helpers + mkdir -p "$dest/usr/libexec" + cp "$PKG_DIR/root/usr/libexec/openclaw-paths.sh" "$dest/usr/libexec/" + cp "$PKG_DIR/root/usr/libexec/openclaw-node.sh" "$dest/usr/libexec/" + chmod +x "$dest/usr/libexec/openclaw-paths.sh" "$dest/usr/libexec/openclaw-node.sh" + # LuCI controller mkdir -p "$dest/usr/lib/lua/luci/controller" cp "$PKG_DIR/luasrc/controller/openclaw.lua" "$dest/usr/lib/lua/luci/controller/" + # Shared Lua helpers + mkdir -p "$dest/usr/lib/lua/openclaw" + cp "$PKG_DIR/luasrc/openclaw/paths.lua" "$dest/usr/lib/lua/openclaw/" + # LuCI CBI mkdir -p "$dest/usr/lib/lua/luci/model/cbi/openclaw" cp "$PKG_DIR/luasrc/model/cbi/openclaw/"*.lua "$dest/usr/lib/lua/luci/model/cbi/openclaw/" diff --git a/tests/test_node_packaging_contract.sh b/tests/test_node_packaging_contract.sh new file mode 100644 index 0000000..b4e7761 --- /dev/null +++ b/tests/test_node_packaging_contract.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) + +BUILD_SCRIPT="$REPO_ROOT/scripts/build-node-musl.sh" +WORKFLOW="$REPO_ROOT/.github/workflows/build-node-musl.yml" +MAKEFILE="$REPO_ROOT/Makefile" +BUILD_IPK="$REPO_ROOT/scripts/build_ipk.sh" +BUILD_RUN="$REPO_ROOT/scripts/build_run.sh" +ENV_SCRIPT="$REPO_ROOT/root/usr/bin/openclaw-env" + +fail() { + echo "FAIL: $1" >&2 + exit 1 +} + +grep -Fq 'patchelf --set-interpreter "/lib/ld-musl-aarch64.so.1"' "$BUILD_SCRIPT" || fail "build script should use system musl loader" +grep -Fq '$ORIGIN/../lib' "$BUILD_SCRIPT" || fail "build script should use relative rpath" +if grep -Fq 'patchelf --set-interpreter "${INSTALL_PREFIX}/lib/ld-musl-aarch64.so.1"' "$BUILD_SCRIPT"; then + fail "build script should not hardcode interpreter to install prefix" +fi + +grep -Fq 'verify_prefix /opt/openclaw/node' "$WORKFLOW" || fail "workflow should verify default install path" +grep -Fq 'verify_prefix /tmp/custom-openclaw-root/openclaw/node' "$WORKFLOW" || fail "workflow should verify custom install path" + +grep -Fq 'oc_node_version_ge "$installed_ver" "$node_ver"' "$ENV_SCRIPT" || fail "installer should enforce minimum node version after extraction" +if grep -Fq 'mirror_list="$mirror_list ${NODE_SELF_HOST}/${v1_tarball}"' "$ENV_SCRIPT"; then + fail "installer should not auto-fallback from V2 to V1 tarball" +fi + +grep -Fq 'openclaw-paths.sh' "$MAKEFILE" || fail "package makefile should install path helper" +grep -Fq 'openclaw-node.sh' "$MAKEFILE" || fail "package makefile should install node helper" +grep -Fq 'openclaw/paths.lua' "$MAKEFILE" || fail "package makefile should install Lua path helper" +grep -Fq 'openclaw-paths.sh' "$BUILD_IPK" || fail "ipk builder should package path helper" +grep -Fq 'openclaw-node.sh' "$BUILD_IPK" || fail "ipk builder should package node helper" +grep -Fq 'openclaw/paths.lua' "$BUILD_IPK" || fail "ipk builder should package Lua path helper" +grep -Fq 'openclaw-paths.sh' "$BUILD_RUN" || fail "run builder should package path helper" +grep -Fq 'openclaw-node.sh' "$BUILD_RUN" || fail "run builder should package node helper" +grep -Fq 'openclaw/paths.lua' "$BUILD_RUN" || fail "run builder should package Lua path helper" + +echo "ok" diff --git a/tests/test_openclaw_node.sh b/tests/test_openclaw_node.sh new file mode 100644 index 0000000..855145e --- /dev/null +++ b/tests/test_openclaw_node.sh @@ -0,0 +1,54 @@ +#!/bin/sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) + +. "$REPO_ROOT/root/usr/libexec/openclaw-node.sh" + +fail() { + echo "FAIL: $1" >&2 + exit 1 +} + +normalized=$(oc_normalize_node_version "v22.16.1") || fail "normalize valid version" +[ "$normalized" = "22.16.1" ] || fail "normalized version value" + +if oc_normalize_node_version "broken-version" >/dev/null 2>&1; then + fail "invalid version should not normalize" +fi + +oc_node_version_ge "22.16.0" "22.16.0" || fail "exact version should satisfy requirement" +oc_node_version_ge "22.16.1" "22.16.0" || fail "newer patch should satisfy requirement" +oc_node_version_ge "23.0.0" "22.16.0" || fail "newer major should satisfy requirement" +if oc_node_version_ge "22.15.1" "22.16.0"; then + fail "older minor version should not satisfy requirement" +fi + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT INT TERM + +cat > "$tmpdir/node-ok" <<'EOF' +#!/bin/sh +if [ "${1:-}" = "--version" ]; then + echo "v22.16.2" + exit 0 +fi +exit 1 +EOF +chmod +x "$tmpdir/node-ok" + +cat > "$tmpdir/node-bad" <<'EOF' +#!/bin/sh +exit 127 +EOF +chmod +x "$tmpdir/node-bad" + +read_ver=$(oc_read_node_version "$tmpdir/node-ok") || fail "read runnable node version" +[ "$read_ver" = "22.16.2" ] || fail "read version value" + +if oc_read_node_version "$tmpdir/node-bad" >/dev/null 2>&1; then + fail "broken node binary should not be accepted" +fi + +echo "ok"