refactor(cli): move self-update to the install scripts; strip Mach-O fully - #1332
Merged
Conversation
The first strip landed as `strip -x` on Mach-O, out of caution that a full strip could leave an image dyld will not load. That caution was wrong for this binary, and it cost a release cycle. `-x` retains external symbols -- 4058 of them on darwin-arm64 -- so the macOS artifacts kept the very symbol table the ELF legs had just shed. Run 30414312364 then flagged exactly those two artifacts (darwin-arm64, ui-darwin-amd64) while every Linux and Windows binary came back clean, which read as the detection "moving to macOS" when it had simply stayed on the unstripped set. Measured on the flagged darwin-arm64 artifact: strip -x 4058 symbols VirusTotal 1 malicious / 61 --strip-all 373 symbols VirusTotal 0 malicious / 61 CLEAN The packaged binary still verifies (`codesign --verify --strict` passes, having been re-signed after stripping) and still runs. Symbol-table removal has now cleared the detection on three independent artifacts across two platforms: linux-amd64 twice, and darwin-arm64 here. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Windows already handed updating to install.ps1, because a running .exe cannot
replace its own image. The remaining platforms now do the same, for a different
and better reason.
An in-process updater is structurally a downloader: it fetches a remote archive,
verifies it, extracts it, marks the result executable and runs it. That composite
lived in every shipped artifact -- along with the release URLs it fetched -- to
serve a command most users run a handful of times. The install scripts already
do all of it, are idempotent (so re-running one IS the update), and run while cbm
is not running.
`update` now prints the exact command for the running platform and exits 0:
unix bash "<dir>/install.sh" (or the curl one-liner when not adjacent)
windows powershell -ExecutionPolicy Bypass -File "<dir>\install.ps1"
Both scripts ship inside their release archive, so the printed path resolves
next to the binary. Flags are still parsed and validated, so `update --dry-run
--ui` keeps rejecting typos rather than silently accepting them.
The updater itself is EXCLUDED from release builds rather than left to dead-code
elimination: build_update_url, download_verify_install, extract_and_install_binary,
checksum fetch, detect_os/detect_arch and the cli_download_* helpers now compile
only under CBM_CLI_ENABLE_TEST_API, which release builds do not define. The C
suite still covers the flow through the activation test seam. Verified against
the release binary: `releases/latest/download` is gone entirely.
Also removes the MCP background update check. It ran on the first tool call and
did:
cbm_popen("curl -sf --max-time 5 -H 'Accept: application/vnd.github+json' "
"'https://api.github.com/repos/.../releases/latest' 2>/dev/null")
An embedded shell command plus a startup network callout, in every session, to
tell users a newer version exists. It shelled out, depended on curl being
installed, and phoned home from every agent session. The install scripts report
versions; this did not need to be in the server.
Smoke follows the same collapse. Phases 6c and 14 asserted a Windows-specific
handoff and a POSIX in-process replacement; they now assert one platform-neutral
contract selected by UPDATE_SCRIPT, and 14a's byte-identity check compares the
driver against ITSELF before and after -- comparing against "$BINARY" passed on
Windows only because Windows has no signing step, while the POSIX fixture
ad-hoc re-signs its copy and so differed before `update` ever ran.
The Linux glibc guard is RELOCATED, not retired. The standard linux asset links
glibc 2.38+ and breaks Debian 11, RHEL 8 and Ubuntu 20.04, so the installer must
fetch the static "-portable" build. That constraint moved from the binary to
install.sh, and the smoke now guards it where the behaviour lives.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two changes, both grounded in measurement rather than inference.
1. Strip Mach-O fully
The first strip landed as
strip -xon Mach-O, out of caution that a full stripcould leave an image dyld will not load. Wrong for this binary, and it cost a
release cycle.
-xretains external symbols — 4058 of them — so the macOS artifacts keptthe symbol table the ELF legs had just shed. Run 30414312364 then flagged
exactly those two artifacts while every Linux and Windows binary came back
clean. That read as the detection "moving to macOS"; it had simply stayed on the
unstripped set.
strip -x(shipped)--strip-allPackaged binary still passes
codesign --verify --strict(re-signed afterstripping) and still runs. Symbol removal has now cleared the detection on
three independent artifacts across two platforms.
2. Self-update out of the product, everywhere
Windows already handed updating to
install.ps1because a running.execannotreplace itself. The other platforms now do the same for a better reason: an
in-process updater is structurally a downloader — fetch, verify, extract,
chmod +x, exec — and that composite shipped in every artifact, with therelease URLs it fetched, to serve a command most users run a handful of times.
updatenow prints the exact command and exits 0. Both install scripts shipinside their archive, so the printed path resolves next to the binary. Flags are
still parsed, so
update --dry-run --uikeeps rejecting typos.The updater is excluded from release builds rather than left to dead-code
elimination —
build_update_url,download_verify_install,extract_and_install_binary, checksum fetch,detect_os/archand thecli_download_*helpers compile only underCBM_CLI_ENABLE_TEST_API. Verifiedagainst the release binary:
releases/latest/downloadis gone.Also removes the MCP background update check, which ran on the first tool call:
An embedded shell command plus a startup network callout in every agent session,
to report that a newer version exists. It shelled out and depended on
curlbeing present. The install scripts report versions.
Smoke
Phases 6c and 14 collapse to one platform-neutral contract selected by
UPDATE_SCRIPT. Two things worth calling out:against
$BINARYpassed on Windows only because Windows has no signing step —the POSIX fixture ad-hoc re-signs its copy, so the two differed before
updateever ran.
links glibc 2.38+ and breaks Debian 11 / RHEL 8 / Ubuntu 20.04, so the
installer must fetch the static
-portablebuild. That constraint moved fromthe binary into
install.sh; the smoke now guards it where the behaviour lives.Verification
climcpdaemon_applicationactivation_transactionlint-ci(cppcheck + clang-format)codesign --verify --strictok, runsNote on the cppcheck fix: the always-true
!update_seam_portablewas resolved byrestructuring the handoff, not by suppressing the warning.