diff --git a/.github/workflows/_smoke.yml b/.github/workflows/_smoke.yml index 75e4506fb..5b8c355bd 100644 --- a/.github/workflows/_smoke.yml +++ b/.github/workflows/_smoke.yml @@ -189,6 +189,13 @@ jobs: chmod +x codebase-memory-mcp scripts/smoke-test.sh ./codebase-memory-mcp + # The -portable binary is what all linux install/update paths now deliver; + # it MUST start on old glibc (Debian 11 / RHEL 8 / Ubuntu 20.04). Runs it + # in debian:bullseye (glibc 2.31) — the standard dynamic binary would fail + # here with `GLIBC_2.38 not found`. + - name: Old-glibc compatibility + run: scripts/ci/check-glibc-compat.sh ./codebase-memory-mcp + - name: Security audits run: | scripts/security-strings.sh ./codebase-memory-mcp diff --git a/install.sh b/install.sh index 3d62249a6..d0678011d 100755 --- a/install.sh +++ b/install.sh @@ -97,10 +97,16 @@ else EXT="tar.gz" fi +# Linux ships a fully-static "-portable" build; the standard linux binary +# dynamically links glibc 2.38+ and fails on older distros (Debian 11, RHEL 8, +# Ubuntu 20.04). macOS/Windows have no such variant. +PORTABLE="" +[ "$OS" = "linux" ] && PORTABLE="-portable" + if [ "$VARIANT" = "ui" ]; then - ARCHIVE="codebase-memory-mcp-ui-${OS}-${ARCH}.${EXT}" + ARCHIVE="codebase-memory-mcp-ui-${OS}-${ARCH}${PORTABLE}.${EXT}" else - ARCHIVE="codebase-memory-mcp-${OS}-${ARCH}.${EXT}" + ARCHIVE="codebase-memory-mcp-${OS}-${ARCH}${PORTABLE}.${EXT}" fi URL="${CBM_DOWNLOAD_URL}/${ARCHIVE}" diff --git a/pkg/npm/install.js b/pkg/npm/install.js index 09873d24d..0b960ef0e 100644 --- a/pkg/npm/install.js +++ b/pkg/npm/install.js @@ -105,7 +105,11 @@ async function main() { fs.mkdirSync(BIN_DIR, { recursive: true }); - const archive = `codebase-memory-mcp-${platform}-${arch}.${ext}`; + // Linux ships a fully-static "-portable" build; the standard linux binary + // dynamically links glibc 2.38+ and fails on older distros. macOS/Windows + // have no such variant. Keep in sync with install.sh / pypi _cli.py / cli.c. + const variant = platform === 'linux' ? '-portable' : ''; + const archive = `codebase-memory-mcp-${platform}-${arch}${variant}.${ext}`; const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archive}`; process.stdout.write(`codebase-memory-mcp: downloading v${VERSION} for ${platform}/${arch}...\n`); diff --git a/pkg/pypi/src/codebase_memory_mcp/_cli.py b/pkg/pypi/src/codebase_memory_mcp/_cli.py index db4b79283..8013359e3 100644 --- a/pkg/pypi/src/codebase_memory_mcp/_cli.py +++ b/pkg/pypi/src/codebase_memory_mcp/_cli.py @@ -153,7 +153,11 @@ def _download(version: str) -> Path: os_name = _os_name() arch = _arch() ext = "zip" if os_name == "windows" else "tar.gz" - archive = f"codebase-memory-mcp-{os_name}-{arch}.{ext}" + # Linux ships a fully-static "-portable" build; the standard linux binary + # dynamically links glibc 2.38+ and fails on older distros. macOS/Windows + # have no such variant. Keep in sync with install.sh / install.js / cli.c. + variant = "-portable" if os_name == "linux" else "" + archive = f"codebase-memory-mcp-{os_name}-{arch}{variant}.{ext}" url = f"https://github.com/{REPO}/releases/download/v{version}/{archive}" _validate_url_scheme(url) diff --git a/scripts/ci/check-glibc-compat.sh b/scripts/ci/check-glibc-compat.sh new file mode 100755 index 000000000..ed0b8ac50 --- /dev/null +++ b/scripts/ci/check-glibc-compat.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Regression guard: the linux binary we ship must START on OLD glibc. +# +# The standard linux release binary dynamically links glibc 2.38+ / GLIBCXX +# 3.4.32 and fails to start on Debian 11, RHEL/Rocky 8, Ubuntu 20.04, Amazon +# Linux 2, etc. The fix points all linux install + self-update paths at the +# fully-static "-portable" asset. This runs a given linux binary inside an +# old-glibc container (debian:bullseye, glibc 2.31) and asserts it starts: +# - RED for the dynamic standard binary (GLIBC_2.38 not found) +# - GREEN for the static -portable binary (runs anywhere) +# +# Usage: check-glibc-compat.sh +# Env: GLIBC_TEST_IMAGE (default: debian:bullseye-slim, glibc 2.31) +set -euo pipefail + +BIN="${1:?usage: check-glibc-compat.sh }" +IMAGE="${GLIBC_TEST_IMAGE:-debian:bullseye-slim}" +BIN_ABS="$(cd "$(dirname "$BIN")" && pwd)/$(basename "$BIN")" + +echo "==> running $(basename "$BIN") --version inside ${IMAGE} (glibc 2.31)" +docker run --rm -v "${BIN_ABS}:/cbm:ro" "${IMAGE}" /cbm --version +echo "PASS: binary starts on old glibc (${IMAGE})" diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index d497d0070..cb8e100a1 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -584,6 +584,17 @@ if ! echo "$UPDATE_OUT" | grep -qi 'standard'; then echo "FAIL: update --dry-run did not respect --standard flag" exit 1 fi +# On Linux the binary must self-update from the static "-portable" asset: the +# standard linux asset dynamically links glibc 2.38+ and breaks on older distros +# (Debian 11, RHEL 8, Ubuntu 20.04). Guards build_update_url in src/cli/cli.c. +if [ "$(uname -s)" = "Linux" ]; then + if ! echo "$UPDATE_OUT" | grep -q -- '-portable'; then + echo "FAIL: linux update --dry-run does not target the -portable asset" + echo "$UPDATE_OUT" + exit 1 + fi + echo "OK: linux update targets the -portable (static) asset" +fi echo "OK: update --dry-run --standard completed" # 6d: config set/get/reset round-trip diff --git a/src/cli/cli.c b/src/cli/cli.c index 4d05285c8..4228dbaad 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -3761,8 +3761,13 @@ static void build_update_url(char *url, int url_sz, const char *os, const char * if (!base_url || !base_url[0]) { base_url = "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download"; } - snprintf(url, url_sz, "%s/codebase-memory-mcp-%s%s-%s.%s", base_url, want_ui ? "ui-" : "", os, - arch, ext); + /* Linux ships a fully-static "-portable" build; the standard linux binary + * dynamically links glibc 2.38+ and fails on older distros. macOS/Windows + * have no such variant. Keep in sync with install.sh / install.js / pypi + * _cli.py. */ + const char *portable = (strcmp(os, "linux") == 0) ? "-portable" : ""; + snprintf(url, url_sz, "%s/codebase-memory-mcp-%s%s-%s%s.%s", base_url, want_ui ? "ui-" : "", os, + arch, portable, ext); } /* Prompt to delete existing indexes. Returns 0 to continue, 1 to abort. */ @@ -3801,8 +3806,10 @@ static int download_verify_install(const char *url, const char *ext, const char } char archive_name[CLI_BUF_256]; - snprintf(archive_name, sizeof(archive_name), "codebase-memory-mcp-%s%s-%s.%s", - want_ui ? "ui-" : "", os, arch, ext); + /* Must match build_update_url: linux uses the static "-portable" asset. */ + const char *portable = (strcmp(os, "linux") == 0) ? "-portable" : ""; + snprintf(archive_name, sizeof(archive_name), "codebase-memory-mcp-%s%s-%s%s.%s", + want_ui ? "ui-" : "", os, arch, portable, ext); int crc = verify_download_checksum(tmp_archive, archive_name); if (crc == CLI_TRUE) { cbm_unlink(tmp_archive);