Skip to content

feat(npm): glibc preflight check on Linux postinstall (#560)#565

Merged
Hmbown merged 1 commit into
Hmbown:mainfrom
Vishnu1837:fix/issue-560-glibc-preflight
May 4, 2026
Merged

feat(npm): glibc preflight check on Linux postinstall (#560)#565
Hmbown merged 1 commit into
Hmbown:mainfrom
Vishnu1837:fix/issue-560-glibc-preflight

Conversation

@Vishnu1837

Copy link
Copy Markdown
Contributor

Summary

What changed

  • New module npm/deepseek-tui/scripts/preflight-glibc.js:
    • Required version: scans the downloaded binary for the highest GLIBC_X.Y symbol (no readelf runtime dependency — pure Node).
    • Host version: getconf GNU_LIBC_VERSION, falling back to ldd --version.
    • Comparison: throws with build-from-source guidance (cargo install / git clone) if host < required, or if no glibc is detected at all (musl/Alpine).
    • Escape hatch: DEEPSEEK_TUI_SKIP_GLIBC_CHECK=1 (or legacy DEEPSEEK_SKIP_GLIBC_CHECK=1) bypasses the check.
  • npm/deepseek-tui/scripts/install.js: invoke preflightGlibc(destination) immediately after verifyChecksum, inside the same try/catch that unlinks the partial download on failure. So a failed preflight leaves nothing behind and npm exits non-zero.

Behavior

  • macOS / Windows: no-op.
  • Linux, host glibc >= required: silent pass (current behavior).
  • Linux, host glibc < required: clear error message naming both versions, pointing at cargo install ... --locked and docs/INSTALL.md, and noting the skip env var. npm marks the install failed.
  • Statically linked / musl-built artifact (no GLIBC_* symbols in the binary): silent pass — preflight does not block.

Why scan bytes instead of readelf

readelf is in binutils and is not guaranteed to be installed on minimal containers. The GLIBC_X.Y symbol versions live in .gnu.version_r as plain ASCII, so a regex scan over the file is reliable and dependency-free. If a future binary is statically linked, the scan returns null and the preflight no-ops.

Test plan

  • Unit-tested the version helpers and detectBinaryRequiredGlibc against a fixture buffer containing GLIBC_2.17, GLIBC_2.28, GLIBC_2.39 — picks 2.39.
  • Confirmed detectBinaryRequiredGlibc returns null on a binary with no GLIBC_* markers.
  • Confirmed preflightGlibc is a no-op on non-Linux even when given a doctored fixture with GLIBC_99.99.
  • Maintainer to verify: actual install on a glibc 2.28 host (e.g. CentOS 8) against a binary built for 2.39 produces the new error and npm install exits non-zero.
  • Maintainer to verify: actual install on glibc 2.39+ host still succeeds.

Related

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
preflightGlibc(destination);
await preflightGlibc(destination);

Comment on lines +53 to +54
const buf = fs.readFileSync(filePath);
const text = buf.toString("latin1");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +29 to +32
const out = execFileSync("getconf", ["GNU_LIBC_VERSION"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using execFileSync blocks the event loop. In an asynchronous script like install.js, it is preferred to use the asynchronous execFile (e.g., via util.promisify(require('child_process').execFile)).

@Hmbown Hmbown merged commit 3179b55 into Hmbown:main May 4, 2026
8 checks passed
Hmbown added a commit that referenced this pull request May 4, 2026
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>
MMMarcinho pushed a commit to MMMarcinho/DeepSeek-TUI that referenced this pull request May 6, 2026
…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
MMMarcinho pushed a commit to MMMarcinho/DeepSeek-TUI that referenced this pull request May 6, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

npm postinstall: glibc preflight check + clear fallback message

2 participants