feat(npm): glibc preflight check on Linux postinstall (#560)#565
Conversation
Linux installs currently succeed even when the host glibc is older than what the prebuilt binary requires, leaving the user with a cryptic `GLIBC_2.XX not found` runtime error. Add a Linux-only preflight in `scripts/preflight-glibc.js` that runs right after checksum verification: - Read the highest required `GLIBC_X.Y` symbol from the downloaded binary by scanning its bytes (no readelf dependency). - Detect host glibc via `getconf GNU_LIBC_VERSION`, falling back to `ldd --version`. - If host < required, throw with a clear message pointing at the build-from-source path (cargo install / git clone instructions). - If glibc cannot be detected at all (musl/Alpine), surface the same guidance instead of installing an incompatible binary. - Skipped on macOS/Windows. `DEEPSEEK_TUI_SKIP_GLIBC_CHECK=1` (or the legacy `DEEPSEEK_SKIP_GLIBC_CHECK=1`) bypasses the check. The downloaded file is unlinked on failure, so a failed preflight leaves nothing behind and npm exits non-zero. Closes Hmbown#560
There was a problem hiding this comment.
Code Review
This pull request introduces a pre-flight glibc version check to the installation script for Linux systems. It adds logic to detect the host's glibc version and compare it against the requirements of the downloaded binary, offering guidance on building from source if an incompatibility is detected. Feedback was provided regarding the synchronous nature of the implementation; specifically, the use of synchronous file reading and process execution may block the event loop and should be converted to asynchronous operations to improve performance and reliability.
| await download(url, destination); | ||
| try { | ||
| await verifyChecksum(destination, assetName, checksums); | ||
| preflightGlibc(destination); |
There was a problem hiding this comment.
The preflightGlibc function performs heavy I/O (reading the entire binary) and executes external processes synchronously. Since ensureBinary is an async function, this call will block the event loop. It is better to make preflightGlibc asynchronous and await it here to maintain consistency with the rest of the installation script.
| preflightGlibc(destination); | |
| await preflightGlibc(destination); |
| const buf = fs.readFileSync(filePath); | ||
| const text = buf.toString("latin1"); |
There was a problem hiding this comment.
Reading the entire binary into memory and converting it to a string can be very inefficient and potentially crash the process if the binary is large (approaching buffer.constants.MAX_STRING_LENGTH). While Rust binaries are typically manageable, this approach creates a memory spike roughly twice the size of the binary. Consider making this function async and using fs.promises.readFile, or ideally, scanning the file in chunks.
| const out = execFileSync("getconf", ["GNU_LIBC_VERSION"], { | ||
| encoding: "utf8", | ||
| stdio: ["ignore", "pipe", "ignore"], | ||
| }); |
Picks up the v0.8.10 patch release contents: * Daemon API quartet for whalescale-desktop integration (#561-#564, PR #567). * Bug cluster: macOS seatbelt cargo registry (#558), MCP SIGTERM shutdown (#420), Linux PR_SET_PDEATHSIG (#421). * npm install on older glibc fix (#555/#560 via #556 + #565). * Shell cwd workspace-boundary validation (#524). * Memory help/docs polish (#497 via #569). * Onboarding language picker (#566). * Whale nicknames interleaved with Simplified Chinese. First-time contributors credited in CHANGELOG: @staryxchen, @shentoumengxin, @Vishnu1837, @20bytes. Workspace `Cargo.toml`, all 9 internal path-dep version pins, and `npm/deepseek-tui/package.json` all bumped to 0.8.10. `Cargo.lock` regenerated and committed alongside. Verified locally: * cargo fmt --all -- --check * cargo clippy --workspace --all-targets --all-features --locked -- -D warnings * cargo test --workspace --all-features --locked * bash scripts/release/check-versions.sh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mbown#565) Linux installs currently succeed even when the host glibc is older than what the prebuilt binary requires, leaving the user with a cryptic `GLIBC_2.XX not found` runtime error. Add a Linux-only preflight in `scripts/preflight-glibc.js` that runs right after checksum verification: - Read the highest required `GLIBC_X.Y` symbol from the downloaded binary by scanning its bytes (no readelf dependency). - Detect host glibc via `getconf GNU_LIBC_VERSION`, falling back to `ldd --version`. - If host < required, throw with a clear message pointing at the build-from-source path (cargo install / git clone instructions). - If glibc cannot be detected at all (musl/Alpine), surface the same guidance instead of installing an incompatible binary. - Skipped on macOS/Windows. `DEEPSEEK_TUI_SKIP_GLIBC_CHECK=1` (or the legacy `DEEPSEEK_SKIP_GLIBC_CHECK=1`) bypasses the check. The downloaded file is unlinked on failure, so a failed preflight leaves nothing behind and npm exits non-zero. Closes Hmbown#560
Picks up the v0.8.10 patch release contents: * Daemon API quartet for whalescale-desktop integration (Hmbown#561-Hmbown#564, PR Hmbown#567). * Bug cluster: macOS seatbelt cargo registry (Hmbown#558), MCP SIGTERM shutdown (Hmbown#420), Linux PR_SET_PDEATHSIG (Hmbown#421). * npm install on older glibc fix (Hmbown#555/Hmbown#560 via Hmbown#556 + Hmbown#565). * Shell cwd workspace-boundary validation (Hmbown#524). * Memory help/docs polish (Hmbown#497 via Hmbown#569). * Onboarding language picker (Hmbown#566). * Whale nicknames interleaved with Simplified Chinese. First-time contributors credited in CHANGELOG: @staryxchen, @shentoumengxin, @Vishnu1837, @20bytes. Workspace `Cargo.toml`, all 9 internal path-dep version pins, and `npm/deepseek-tui/package.json` all bumped to 0.8.10. `Cargo.lock` regenerated and committed alongside. Verified locally: * cargo fmt --all -- --check * cargo clippy --workspace --all-targets --all-features --locked -- -D warnings * cargo test --workspace --all-features --locked * bash scripts/release/check-versions.sh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
GLIBC_2.XX not found.What changed
npm/deepseek-tui/scripts/preflight-glibc.js:GLIBC_X.Ysymbol (noreadelfruntime dependency — pure Node).getconf GNU_LIBC_VERSION, falling back toldd --version.DEEPSEEK_TUI_SKIP_GLIBC_CHECK=1(or legacyDEEPSEEK_SKIP_GLIBC_CHECK=1) bypasses the check.npm/deepseek-tui/scripts/install.js: invokepreflightGlibc(destination)immediately afterverifyChecksum, inside the sametry/catchthat unlinks the partial download on failure. So a failed preflight leaves nothing behind and npm exits non-zero.Behavior
cargo install ... --lockedanddocs/INSTALL.md, and noting the skip env var. npm marks the install failed.GLIBC_*symbols in the binary): silent pass — preflight does not block.Why scan bytes instead of
readelfreadelfis inbinutilsand is not guaranteed to be installed on minimal containers. TheGLIBC_X.Ysymbol versions live in.gnu.version_ras plain ASCII, so a regex scan over the file is reliable and dependency-free. If a future binary is statically linked, the scan returnsnulland the preflight no-ops.Test plan
detectBinaryRequiredGlibcagainst a fixture buffer containingGLIBC_2.17,GLIBC_2.28,GLIBC_2.39— picks2.39.detectBinaryRequiredGlibcreturnsnullon a binary with noGLIBC_*markers.preflightGlibcis a no-op on non-Linux even when given a doctored fixture withGLIBC_99.99.npm installexits non-zero.Related