From 86f113762b179f5154160b26a37afeb511590d48 Mon Sep 17 00:00:00 2001 From: Sean Dolbec Date: Sat, 31 Oct 2026 14:10:56 -0400 Subject: [PATCH] CodexBar: bump upstream to v0.83.0 and retire the libxml2 shim when unneeded Upstream v0.79.0 stopped linking libxml2.so.2, so the compat wrapper is no longer needed on Ubuntu 26.04. codexbar_install_cli_compat now points codexbar-upstream straight at the current binary (a rename, never missing) and removes compat/ and .real when that binary does not need libxml2. When an install moves a plain binary over the wrapper it also drops the remembered previous binary instead of wrapping it again. test-codexbar-cli-compat.py covers upstream's symlink layout and the plain-binary layout. Verified in an arm64 Ubuntu 26.04 container: both v0.83.0 tarballs pass sha256; every CLI form our helpers use parses (only signed-out provider errors); --all-accounts/--account, the grok provider and CODEX_HOME still work; the live 0.78-behind-wrapper layout upgrades to a clean symlink running 0.83.0. Upstream's Linux settings QML and snapshot() are unchanged since v0.78.0. Not installed or deployed on the j314s. --- mseries/CODEXBAR-PORT.md | 7 +- mseries/OS9-HANDOFF.md | 4 + mseries/install-codexbar.sh | 4 +- mseries/lib-codexbar.sh | 35 +++++++- mseries/t3-wingpanel/README.md | 2 +- mseries/test-codexbar-cli-compat.py | 130 ++++++++++++++++++++++++++++ mseries/validate.sh | 1 + 7 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 mseries/test-codexbar-cli-compat.py diff --git a/mseries/CODEXBAR-PORT.md b/mseries/CODEXBAR-PORT.md index b317b94f7..6ee6ca1b0 100644 --- a/mseries/CODEXBAR-PORT.md +++ b/mseries/CODEXBAR-PORT.md @@ -37,6 +37,11 @@ ones.** Upgrade by re-pinning, re-installing upstream, and re-verifying each ite 7. If upstream starts reporting a provider we draw ourselves (a `claude` entry), our card steps aside; check for double cards. 8. Record the pin, what was re-verified and what was not in `OS9-HANDOFF.md`; push; run `deploy-codexbar.sh`. +`codexbar --version` reads the `VERSION` file the tarball installs beside the binary, so it can name the new +release while an old binary still runs. Confirm what `~/.local/lib/codexbar-cli/codexbar-upstream` resolves to. +v0.78.0 linked `libxml2.so.2` and ran through a compat wrapper on this base (`codexbar_install_cli_compat`); +v0.79.0+ does not, and the next install or deploy replaces the wrapper with a symlink to `CodexBarCLI`. + ## Do not lose - Our helpers are installed from this repo by the installers and the deploy script; the installer does not delete user config. @@ -459,7 +464,7 @@ This handles Pro-to-Max upgrades whose issued token still says Pro. ## Unified T3 presentation — 2026-10-26 `mseries/t3-wingpanel` embeds this maintained native view and RepoBar's view in -one T3 menu, shared with the dock. The backend pin remains v0.78.0 and the +one T3 menu, shared with the dock. The backend pin is v0.83.0 (bumped 2026-10-31) and the `codexbar-linux --snapshot` schema is unchanged. Supported standalone deployments retain a rollback module outside Wingpanel discovery when `libmseries-t3.so` is installed; helpers/settings/provider assets remain maintained. Native module diff --git a/mseries/OS9-HANDOFF.md b/mseries/OS9-HANDOFF.md index 743fbbc12..0d1b6cd6d 100644 --- a/mseries/OS9-HANDOFF.md +++ b/mseries/OS9-HANDOFF.md @@ -1,3 +1,7 @@ +## CodexBar upstream v0.78.0 -> v0.83.0 (staged, not deployed) — 2026-10-31 + +Bumped `install-codexbar.sh` to v0.83.0. Read the v0.79.0-v0.83.0 release notes; both aarch64 tarballs pass their published sha256. In an arm64 Ubuntu 26.04 container, v0.83.0 accepts every CLI form our helpers use (`usage --provider codex|claude|opencodego [--source api] --format json`, `config providers --format json`, `config set-api-key --provider ... --stdin`); the only errors were "signed out" provider errors, not parsing errors (v0.83.0 rejects malformed arguments). `--all-accounts`/`--account` still exist, `grok` is still listed, `CODEX_HOME` is still honored. Upstream `Integrations/Linux`: the settings QML is unchanged, `snapshot()` is unchanged, `DesktopController::probe` now drops rows for other providers in custom mode, and `install.py` handles odd paths. v0.79.0+ no longer links `libxml2.so.2`: `codexbar_install_cli_compat` now retires its wrapper (symlink to `CodexBarCLI`, removes `compat/` and `.real`) when the current CLI does not need it, and drops a stale remembered binary when a plain binary is installed; new `test-codexbar-cli-compat.py` (6 cases); a container run using the live 0.78-behind-wrapper layout ends on a clean symlink running 0.83.0. Not done: install/deploy on the j314s (not reachable from this session), `check-codexbar-port.sh` against the live app, the snapshot fixture comparison and a signed-in usage read. Next: on the j314s run `bash mseries/install-codexbar.sh --skip-system-deps`, `bash mseries/deploy-codexbar.sh`, `bash mseries/check-codexbar-port.sh`. + ## Warren local AI and eGPU updates — 2026-10-30 Installed `mseries-nvidia-open-source` `595.112.07+apple.1-1` (SHA-256 `a397b68a...b209ab23`) containing the three prepared NVIDIA fixes and license at `/usr/src/mseries-nvidia-open-595.112.07`. This is source only; no NVIDIA module or matching CUDA userspace is installed. The [source package script](nvidia-egpu/package-source.sh) verifies the exact prepared diff before packaging. diff --git a/mseries/install-codexbar.sh b/mseries/install-codexbar.sh index 491311ad8..af5e0c264 100755 --- a/mseries/install-codexbar.sh +++ b/mseries/install-codexbar.sh @@ -4,7 +4,7 @@ set -euo pipefail [[ $(uname -m) == aarch64 ]] || { echo 'This installer requires ARM64' >&2; exit 1; } repo=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) source "$repo/mseries/lib-apt.sh" -version=v0.78.0 +version=v0.83.0 if [[ ${1:-} != --skip-system-deps ]]; then mseries_apt_refresh sudo apt-get install -y \ @@ -27,7 +27,7 @@ source "$repo/mseries/lib-codexbar.sh" codexbar_install_helpers "$repo" tar -xzf "CodexBarCLI-${version}-linux-aarch64.tar.gz" -C "$HOME/.local/lib/codexbar-cli" mv -f "$HOME/.local/lib/codexbar-cli/codexbar" "$HOME/.local/lib/codexbar-cli/codexbar-upstream" -# v0.78.0's CLI links libxml2.so.2; wrap it when this base only ships the newer soname. +# v0.78.0's CLI linked libxml2.so.2 and needed a wrapper on this base; v0.79.0+ does not, and the shim removes itself. codexbar_install_cli_compat codexbar_install_wrapper "$repo" tar -xzf "CodexBarDesktop-${version}-linux-aarch64.tar.gz" diff --git a/mseries/lib-codexbar.sh b/mseries/lib-codexbar.sh index 5df560641..60c854090 100644 --- a/mseries/lib-codexbar.sh +++ b/mseries/lib-codexbar.sh @@ -34,16 +34,43 @@ codexbar_install_helpers() { codexbar_install_cli_compat } +# codexbar_cli_needs_libxml2 BINARY: true when the CLI binary links libxml2.so.2. +codexbar_cli_needs_libxml2() { + LC_ALL=C ldd "$1" 2>/dev/null | grep -q 'libxml2\.so\.2' +} + # codexbar_install_cli_compat: upstream v0.78.0's CLI links libxml2.so.2, but # libxml2 >= 2.14 renamed the soname (libxml2.so.16 on this base), so the CLI # cannot load. Resolve the old soname from a compat directory and route # codexbar-upstream through a wrapper that points the loader there. The wrapper -# is a no-op when the system provides libxml2.so.2 (an older base, or an upstream -# rebuild); delete this shim when that day comes. Idempotent: the real binary's -# name is remembered in codexbar-upstream.real. +# is a no-op when the system provides libxml2.so.2 (an older base). Upstream +# v0.79.0 and later no longer link libxml2; for them the shim removes itself. +# Idempotent: the real binary's name is remembered in codexbar-upstream.real. codexbar_install_cli_compat() { - local lib="$HOME/.local/lib/codexbar-cli" soname real base + local lib="$HOME/.local/lib/codexbar-cli" soname real base current [[ -e $lib/codexbar-upstream || -L $lib/codexbar-upstream ]] || return 0 + if [[ -f $lib/codexbar-upstream && ! -L $lib/codexbar-upstream \ + && $(head -c 2 "$lib/codexbar-upstream") == '#!' ]]; then + # Our wrapper: the binary it runs is the current CLI. + current=$lib/$(cat "$lib/codexbar-upstream.real" 2>/dev/null) + else + # An install moved upstream's codexbar (a symlink to CodexBarCLI, or a plain binary) + # over the wrapper. It is current; a remembered name may point at the previous version. + current=$(readlink -f "$lib/codexbar-upstream") + [[ $current == "$lib/codexbar-upstream.bin" ]] || rm -f "$lib/codexbar-upstream.bin" + rm -f "$lib/codexbar-upstream.real" + fi + if [[ -f $current ]] && ! codexbar_cli_needs_libxml2 "$current"; then + # Upstream v0.79.0+ does not link libxml2: point codexbar-upstream straight at the + # binary (rename, so callers never find it missing) and drop the shim. + if [[ $current != "$(readlink -f "$lib/codexbar-upstream")" ]]; then + ln -sfn "$(basename "$current")" "$lib/codexbar-upstream.new" + mv -f "$lib/codexbar-upstream.new" "$lib/codexbar-upstream" + fi + rm -f "$lib/codexbar-upstream.real" + rm -rf "$lib/compat" + return 0 + fi if ldconfig -p | awk '{print $1}' | grep -qx libxml2.so.2; then return 0 fi diff --git a/mseries/t3-wingpanel/README.md b/mseries/t3-wingpanel/README.md index 9e02268f4..55182a2e5 100644 --- a/mseries/t3-wingpanel/README.md +++ b/mseries/t3-wingpanel/README.md @@ -14,7 +14,7 @@ requires a recent, connected, non-stale feed and live application bus owner; a one-shot expiry clears an idle badge without polling collectors. One normal RepoBar object retains the existing background notification/settings behavior; the visible embedded GitHub view never duplicates notifications. CodexBar's -v0.78.0 custom collectors, accounts, sessions, wrappers and daemon stay in use. +custom collectors, accounts, sessions, wrappers and daemon stay in use. ## Build and stage diff --git a/mseries/test-codexbar-cli-compat.py b/mseries/test-codexbar-cli-compat.py new file mode 100644 index 000000000..f55435ee6 --- /dev/null +++ b/mseries/test-codexbar-cli-compat.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""codexbar_install_cli_compat: the libxml2 shim must follow upstream upgrades and remove itself +once the CLI no longer links libxml2.so.2 (upstream v0.79.0+).""" + +import os +import pathlib +import subprocess +import tempfile +import unittest + +LIB_SH = pathlib.Path(__file__).with_name("lib-codexbar.sh") +# Fake binaries; the stub below reports libxml2 for any file containing "needs-libxml2". +OLD = b"\x7fELF v0.78.0 needs-libxml2" +NEW = b"\x7fELF v0.83.0" +NEW_NEEDS = b"\x7fELF v0.8x needs-libxml2" + + +class CliCompat(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + root = pathlib.Path(self.temp.name) + self.home = root / "home" + self.lib = self.home / ".local/lib/codexbar-cli" + self.lib.mkdir(parents=True) + shims = root / "bin" + shims.mkdir() + # A base that ships only the renamed libxml2 soname, like Ubuntu 26.04. + (shims / "ldconfig").write_text( + "#!/bin/sh\nprintf '\\tlibxml2.so.16 (libc6,AArch64) => /usr/lib/libxml2.so.16\\n'\n") + (shims / "ldconfig").chmod(0o755) + self.path = f"{shims}:{os.environ['PATH']}" + + def compat(self): + script = (f'source "{LIB_SH}"\n' + 'codexbar_cli_needs_libxml2() { grep -q needs-libxml2 "$1"; }\n' + 'codexbar_install_cli_compat\n') + subprocess.run(["bash", "-c", script], check=True, + env={**os.environ, "HOME": str(self.home), "PATH": self.path}) + + def install(self, payload, symlink=True): + """install-codexbar.sh: extract the tarball into lib, then move codexbar over codexbar-upstream. + Upstream's tarball ships codexbar as a symlink to CodexBarCLI.""" + extracted = self.lib / "codexbar" + if symlink: + (self.lib / "CodexBarCLI").write_bytes(payload) + extracted.symlink_to("CodexBarCLI") + else: + extracted.write_bytes(payload) + os.replace(extracted, self.lib / "codexbar-upstream") + + def executes(self): + """Bytes of the binary codexbar-upstream ends up running.""" + upstream = self.lib / "codexbar-upstream" + if not upstream.is_symlink() and upstream.read_bytes().startswith(b"#!"): + text = upstream.read_text() + self.assertIn("compat", text) + return (self.lib / (self.lib / "codexbar-upstream.real").read_text().strip()).read_bytes() + return upstream.read_bytes() + + def assert_no_shim(self): + for leftover in ("codexbar-upstream.real", "codexbar-upstream.bin", "compat"): + self.assertFalse((self.lib / leftover).exists(), leftover) + + def test_cli_that_links_libxml2_gets_the_wrapper(self): + self.install(OLD) + self.compat() + + self.assertTrue((self.lib / "compat/libxml2.so.2").is_symlink()) + self.assertEqual((self.lib / "codexbar-upstream.real").read_text().strip(), "CodexBarCLI") + self.assertEqual(self.executes(), OLD) + + def test_rerun_on_wrapper_is_idempotent(self): + self.install(OLD) + self.compat() + wrapper = (self.lib / "codexbar-upstream").read_bytes() + + self.compat() + + self.assertEqual((self.lib / "codexbar-upstream").read_bytes(), wrapper) + self.assertEqual(self.executes(), OLD) + + def test_upgrade_to_cli_without_libxml2_removes_the_shim(self): + self.install(OLD) + self.compat() + + self.install(NEW) + self.compat() + + self.assertTrue((self.lib / "codexbar-upstream").is_symlink()) + self.assertEqual(self.executes(), NEW) + self.assert_no_shim() + + def test_wrapper_left_over_a_replaced_binary_is_retired(self): + # A v0.83.0 tarball extracted over CodexBarCLI while the wrapper stayed (the previous + # code's upgrade result): the next deploy points codexbar-upstream at the binary directly. + self.install(OLD) + self.compat() + (self.lib / "CodexBarCLI").write_bytes(NEW) + + self.compat() + + self.assertTrue((self.lib / "codexbar-upstream").is_symlink()) + self.assertEqual(self.executes(), NEW) + self.assert_no_shim() + + def test_plain_binary_upgrade_that_still_needs_libxml2_wraps_the_new_binary(self): + self.install(OLD, symlink=False) + self.compat() + self.assertEqual((self.lib / "codexbar-upstream.real").read_text().strip(), "codexbar-upstream.bin") + + self.install(NEW_NEEDS, symlink=False) + self.compat() + + self.assertEqual(self.executes(), NEW_NEEDS) + + def test_plain_binary_upgrade_without_libxml2_drops_the_old_binary(self): + self.install(OLD, symlink=False) + self.compat() + + self.install(NEW, symlink=False) + self.compat() + self.compat() + + self.assertEqual(self.executes(), NEW) + self.assert_no_shim() + + +if __name__ == "__main__": + unittest.main() diff --git a/mseries/validate.sh b/mseries/validate.sh index 9d5af6e2e..a051f90f3 100755 --- a/mseries/validate.sh +++ b/mseries/validate.sh @@ -265,6 +265,7 @@ check codexbar 'test-codexbar-claude.py' 'python3 mseries/test-codexbar-claude.p check codexbar 'test-codexbar-claude-history.py' 'python3 mseries/test-codexbar-claude-history.py' check codexbar 'test-codexbar-claude-handoff.py' 'python3 mseries/test-codexbar-claude-handoff.py' check codexbar 'test-codexbar-wrapper.py' 'python3 mseries/test-codexbar-wrapper.py' +check codexbar 'test-codexbar-cli-compat.py' 'python3 mseries/test-codexbar-cli-compat.py' check codexbar 'test-codexbar-opencode-local.py' 'python3 mseries/test-codexbar-opencode-local.py' check codexbar 'test-codexbar-opencode-login.py' 'python3 mseries/test-codexbar-opencode-login.py'