mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(install): bound finalization probes (#86997)
Bounds nonessential installer finalization probes so npm prefix and daemon-status checks warn and fall back instead of hanging setup. Thanks @giodl73-repo!
This commit is contained in:
+49
-4
@@ -2269,7 +2269,7 @@ ensure_user_local_bin_on_path() {
|
||||
|
||||
npm_global_bin_dir() {
|
||||
local prefix=""
|
||||
prefix="$(npm prefix -g 2>/dev/null || true)"
|
||||
prefix="$(bounded_probe_output "npm prefix -g" npm prefix -g || true)"
|
||||
if [[ -n "$prefix" ]]; then
|
||||
if [[ "$prefix" == /* ]]; then
|
||||
echo "${prefix%/}/bin"
|
||||
@@ -2277,7 +2277,7 @@ npm_global_bin_dir() {
|
||||
fi
|
||||
fi
|
||||
|
||||
prefix="$(npm config get prefix 2>/dev/null || true)"
|
||||
prefix="$(bounded_probe_output "npm config get prefix" npm config get prefix || true)"
|
||||
if [[ -n "$prefix" && "$prefix" != "undefined" && "$prefix" != "null" ]]; then
|
||||
if [[ "$prefix" == /* ]]; then
|
||||
echo "${prefix%/}/bin"
|
||||
@@ -2496,6 +2496,51 @@ maybe_nodenv_rehash() {
|
||||
fi
|
||||
}
|
||||
|
||||
bounded_probe_output() {
|
||||
local label="$1"
|
||||
shift
|
||||
local timeout_seconds="${OPENCLAW_INSTALL_PROBE_TIMEOUT_SECONDS:-5}"
|
||||
local output_file status_file timeout_file pid watchdog status
|
||||
output_file="$(mktemp)"
|
||||
status_file="$(mktemp)"
|
||||
timeout_file="$(mktemp)"
|
||||
TMPFILES+=("$output_file" "$status_file" "$timeout_file")
|
||||
|
||||
(
|
||||
"$@" >"$output_file" 2>/dev/null
|
||||
printf '%s' "$?" >"$status_file"
|
||||
) &
|
||||
pid="$!"
|
||||
|
||||
(
|
||||
sleep "$timeout_seconds"
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
printf '1' >"$timeout_file"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 0.1
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
printf 'timeout' >"$status_file"
|
||||
fi
|
||||
) &
|
||||
watchdog="$!"
|
||||
|
||||
wait "$pid" 2>/dev/null || true
|
||||
kill "$watchdog" 2>/dev/null || true
|
||||
wait "$watchdog" 2>/dev/null || true
|
||||
|
||||
status="$(cat "$status_file" 2>/dev/null || true)"
|
||||
if [[ -s "$timeout_file" || "$status" == "timeout" ]]; then
|
||||
echo "Warning: timed out during installer finalization probe: ${label}" >&2
|
||||
return 124
|
||||
fi
|
||||
|
||||
cat "$output_file" 2>/dev/null || true
|
||||
if [[ -n "$status" && "$status" =~ ^[0-9]+$ ]]; then
|
||||
return "$status"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
warn_openclaw_not_found() {
|
||||
ui_warn "Installed, but openclaw is not discoverable on PATH in this shell"
|
||||
echo " Try: hash -r (bash) or rehash (zsh), then retry."
|
||||
@@ -2509,7 +2554,7 @@ warn_openclaw_not_found() {
|
||||
fi
|
||||
|
||||
local npm_prefix=""
|
||||
npm_prefix="$(npm prefix -g 2>/dev/null || true)"
|
||||
npm_prefix="$(bounded_probe_output "npm prefix -g" npm prefix -g || true)"
|
||||
local npm_bin=""
|
||||
npm_bin="$(npm_global_bin_dir 2>/dev/null || true)"
|
||||
if [[ -n "$npm_prefix" ]]; then
|
||||
@@ -2906,7 +2951,7 @@ is_gateway_daemon_loaded() {
|
||||
fi
|
||||
|
||||
local status_json=""
|
||||
status_json="$("$claw" daemon status --json 2>/dev/null || true)"
|
||||
status_json="$(bounded_probe_output "openclaw daemon status --json" "$claw" daemon status --json || true)"
|
||||
if [[ -z "$status_json" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -138,7 +138,9 @@ describe("install.sh", () => {
|
||||
expect(script).toContain(
|
||||
'run_quiet_step "Installing Node.js" sudo apk add --no-cache nodejs npm',
|
||||
);
|
||||
expect(script).toContain('run_quiet_step "Installing nodejs-current" apk add --no-cache nodejs-current npm');
|
||||
expect(script).toContain(
|
||||
'run_quiet_step "Installing nodejs-current" apk add --no-cache nodejs-current npm',
|
||||
);
|
||||
expect(script).toContain("if ! node_is_at_least_required; then");
|
||||
|
||||
const apkIndex = script.indexOf("if command -v apk &> /dev/null && is_alpine_linux; then");
|
||||
@@ -208,7 +210,9 @@ describe("install.sh", () => {
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("step:Installing Node.js|apk add --no-cache nodejs npm");
|
||||
expect(result.stdout).toContain("warn:Alpine nodejs package installed v20.15.1");
|
||||
expect(result.stdout).toContain("step:Installing nodejs-current|apk add --no-cache nodejs-current npm");
|
||||
expect(result.stdout).toContain(
|
||||
"step:Installing nodejs-current|apk add --no-cache nodejs-current npm",
|
||||
);
|
||||
expect(result.stdout).toContain("finish-linux-node");
|
||||
});
|
||||
|
||||
@@ -247,8 +251,12 @@ describe("install.sh", () => {
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("warn:Alpine nodejs package installed v20.15.1");
|
||||
expect(result.stdout).toContain("step:Installing nodejs-current|apk add --no-cache nodejs-current npm");
|
||||
expect(result.stdout).toContain("error:Alpine apk repositories did not provide Node.js v22.19+");
|
||||
expect(result.stdout).toContain(
|
||||
"step:Installing nodejs-current|apk add --no-cache nodejs-current npm",
|
||||
);
|
||||
expect(result.stdout).toContain(
|
||||
"error:Alpine apk repositories did not provide Node.js v22.19+",
|
||||
);
|
||||
expect(result.stdout).toContain("Use Alpine 3.21+ or install Node.js 24 manually");
|
||||
});
|
||||
|
||||
@@ -758,6 +766,64 @@ describe("install.sh", () => {
|
||||
expect(result.stdout).not.toContain("[4/3] Verifying installation");
|
||||
});
|
||||
|
||||
it("bounds installer npm prefix probes during finalization helpers", () => {
|
||||
const result = runInstallShell(
|
||||
[
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
"npm() {",
|
||||
' if [[ "$1" == "prefix" && "$2" == "-g" ]]; then sleep 2; return 0; fi',
|
||||
' if [[ "$1" == "config" && "$2" == "get" && "$3" == "prefix" ]]; then printf "/tmp/openclaw-npm\\n"; return 0; fi',
|
||||
" return 1",
|
||||
"}",
|
||||
"npm_global_bin_dir",
|
||||
].join("\n"),
|
||||
{ OPENCLAW_INSTALL_PROBE_TIMEOUT_SECONDS: "0.1" },
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout.trim()).toBe("/tmp/openclaw-npm/bin");
|
||||
expect(result.stderr).toContain("timed out during installer finalization probe: npm prefix -g");
|
||||
});
|
||||
|
||||
it("bounds daemon status probes during finalization helpers", () => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-probe-"));
|
||||
const claw = join(tmp, "openclaw");
|
||||
writeFileSync(
|
||||
claw,
|
||||
[
|
||||
"#!/usr/bin/env bash",
|
||||
'if [[ "$1" == "daemon" && "$2" == "status" && "$3" == "--json" ]]; then',
|
||||
" sleep 2",
|
||||
" exit 0",
|
||||
"fi",
|
||||
"exit 1",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
chmodSync(claw, 0o755);
|
||||
try {
|
||||
const result = runInstallShell(
|
||||
[
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`if is_gateway_daemon_loaded ${JSON.stringify(claw)}; then`,
|
||||
' printf "loaded\\n"',
|
||||
"else",
|
||||
' printf "not-loaded\\n"',
|
||||
"fi",
|
||||
].join("\n"),
|
||||
{ OPENCLAW_INSTALL_PROBE_TIMEOUT_SECONDS: "0.1" },
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout.trim()).toBe("not-loaded");
|
||||
expect(result.stderr).toContain(
|
||||
"timed out during installer finalization probe: openclaw daemon status --json",
|
||||
);
|
||||
} finally {
|
||||
rmSync(tmp, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("loads nvm before checking Node.js so stale system Node does not win", () => {
|
||||
expect(script).toMatch(
|
||||
/# Step 2: Node\.js\s+load_nvm_for_node_detection\s+if ! check_node; then/,
|
||||
|
||||
Reference in New Issue
Block a user