From 69c44e4238dd69dc1fec218a1d1ab9eb59bfea03 Mon Sep 17 00:00:00 2001 From: echobt Date: Thu, 29 Jan 2026 14:22:02 +0000 Subject: [PATCH] feat(release): add static musl builds for portable Linux binaries This change adds support for statically-linked Linux binaries using musl libc, which eliminates GLIBC version dependencies and allows the CLI to run on older Linux distributions like Ubuntu 22.04 LTS, Debian 12, and Linux Mint 21.x. Changes: - Add x86_64-unknown-linux-musl and aarch64-unknown-linux-musl build targets - Create .cargo/config.toml with musl-specific linker configuration - Update keyring dependency to use platform-specific features: - linux-native for glibc builds (libsecret/D-Bus) - linux-native for musl builds (uses kernel keyutils) - apple-native for macOS - windows-native for Windows - Add musl toolchain installation in release workflow - Add binary verification step to confirm static linking The static builds will be available as: - cortex-cli-linux-x64-static.tar.gz - cortex-cli-linux-arm64-static.tar.gz Resolves: GLIBC 2.38+ requirement on Ubuntu 22.04/Debian 12 --- .cargo/config.toml | 42 +++++++++++++++ .github/workflows/release.yml | 95 ++++++++++++++++++++++++++++++++- cortex-engine/Cargo.toml | 15 ++++-- cortex-keyring-store/Cargo.toml | 20 ++++++- cortex-login/Cargo.toml | 14 ++++- 5 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..a3b4bd8b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,42 @@ +# Cargo configuration for static linking with musl targets +# This enables building portable, statically-linked Linux binaries + +# ============================================================================= +# x86_64 musl target (static linking) +# ============================================================================= +[target.x86_64-unknown-linux-musl] +# Use the musl-cross toolchain linker for cross-compilation +# If building on a musl-based system, this can be simplified to just "musl-gcc" +rustflags = [ + "-C", "target-feature=+crt-static", + "-C", "link-self-contained=yes" +] + +# ============================================================================= +# aarch64 musl target (static linking) +# ============================================================================= +[target.aarch64-unknown-linux-musl] +rustflags = [ + "-C", "target-feature=+crt-static", + "-C", "link-self-contained=yes" +] + +# ============================================================================= +# Build configuration +# ============================================================================= +[build] +# Use nightly for multithreaded compilation (set via RUSTFLAGS in CI) +# rustflags = ["-Zthreads=32"] + +# ============================================================================= +# Net configuration (for faster crate downloads) +# ============================================================================= +[net] +retry = 3 +git-fetch-with-cli = true + +# ============================================================================= +# Registry configuration (sparse protocol for faster index updates) +# ============================================================================= +[registries.crates-io] +protocol = "sparse" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8646b394..c8c1f39a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,27 +118,47 @@ jobs: target: x86_64-pc-windows-msvc artifact: cortex-cli-windows-x64 ext: .exe + static: false # Temporarily disabled: Windows ARM64 build has LLVM/clang issues # - runner: blacksmith-32vcpu-windows-2025 # target: aarch64-pc-windows-msvc # artifact: cortex-cli-windows-arm64 # ext: .exe + # static: false - runner: macos-latest target: x86_64-apple-darwin artifact: cortex-cli-macos-x64 ext: "" + static: false - runner: macos-latest target: aarch64-apple-darwin artifact: cortex-cli-macos-arm64 ext: "" + static: false - runner: blacksmith-32vcpu-ubuntu-2404 target: x86_64-unknown-linux-gnu artifact: cortex-cli-linux-x64 ext: "" + static: false - runner: blacksmith-32vcpu-ubuntu-2404-arm target: aarch64-unknown-linux-gnu artifact: cortex-cli-linux-arm64 ext: "" + static: false + # ================================================================= + # Static musl builds - portable across Linux distributions + # These binaries have no GLIBC dependency and work on older systems + # ================================================================= + - runner: blacksmith-32vcpu-ubuntu-2404 + target: x86_64-unknown-linux-musl + artifact: cortex-cli-linux-x64-static + ext: "" + static: true + - runner: blacksmith-32vcpu-ubuntu-2404-arm + target: aarch64-unknown-linux-musl + artifact: cortex-cli-linux-arm64-static + ext: "" + static: true steps: - uses: actions/checkout@v4 @@ -162,6 +182,30 @@ jobs: prefix-key: "rust-release-cli-${{ matrix.target }}" shared-key: ${{ needs.prepare.outputs.cache_key }} + # ========================================================================= + # Static musl build setup (for portable Linux binaries) + # ========================================================================= + - name: Install musl toolchain (x86_64 static) + if: matrix.target == 'x86_64-unknown-linux-musl' + run: | + sudo apt-get update + sudo apt-get install -y musl-tools musl-dev + # Verify musl-gcc is available + which musl-gcc + musl-gcc --version + + - name: Install musl toolchain (aarch64 static) + if: matrix.target == 'aarch64-unknown-linux-musl' + run: | + sudo apt-get update + # For cross-compiling to aarch64-musl, we need the cross toolchain + sudo apt-get install -y musl-tools musl-dev gcc-aarch64-linux-gnu + # Install aarch64-linux-musl-gcc cross compiler + wget -q https://musl.cc/aarch64-linux-musl-cross.tgz + tar xzf aarch64-linux-musl-cross.tgz + sudo mv aarch64-linux-musl-cross /opt/ + echo "/opt/aarch64-linux-musl-cross/bin" >> $GITHUB_PATH + # Windows ARM64 requires clang from Visual Studio for building the ring crate # See: https://github.com/briansmith/ring/blob/main/BUILDING.md - name: Setup MSVC environment for Windows ARM64 @@ -215,12 +259,61 @@ jobs: echo "C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append } - - name: Build release binary + # ========================================================================= + # Build commands + # ========================================================================= + - name: Build release binary (dynamic) + if: matrix.static == false run: cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli env: RUSTFLAGS: "-Zthreads=32" CARGO_PROFILE_RELEASE_LTO: thin + - name: Build release binary (static musl x86_64) + if: matrix.target == 'x86_64-unknown-linux-musl' + run: | + cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli + env: + RUSTFLAGS: "-Zthreads=32 -C target-feature=+crt-static" + CARGO_PROFILE_RELEASE_LTO: thin + CC_x86_64_unknown_linux_musl: musl-gcc + AR_x86_64_unknown_linux_musl: ar + CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc + + - name: Build release binary (static musl aarch64) + if: matrix.target == 'aarch64-unknown-linux-musl' + run: | + cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli + env: + RUSTFLAGS: "-Zthreads=32 -C target-feature=+crt-static" + CARGO_PROFILE_RELEASE_LTO: thin + CC_aarch64_unknown_linux_musl: aarch64-linux-musl-gcc + AR_aarch64_unknown_linux_musl: aarch64-linux-musl-ar + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-musl-gcc + + # ========================================================================= + # Verify static binary (for musl builds) + # ========================================================================= + - name: Verify static binary + if: matrix.static == true + run: | + echo "=== Verifying static binary ===" + BINARY="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" + file "$BINARY" + echo "" + echo "=== Checking dynamic dependencies ===" + # For a truly static binary, ldd should report "not a dynamic executable" + # or show no dynamic dependencies + if ldd "$BINARY" 2>&1 | grep -q "not a dynamic executable\|statically linked"; then + echo "✅ Binary is statically linked" + else + echo "⚠️ Binary has some dynamic dependencies (expected for musl with linux-keyutils):" + ldd "$BINARY" 2>&1 || true + fi + echo "" + echo "=== Binary size ===" + ls -lh "$BINARY" + - name: Prepare artifact (Unix) if: runner.os != 'Windows' run: | diff --git a/cortex-engine/Cargo.toml b/cortex-engine/Cargo.toml index 20fbb8ab..3614c9a7 100644 --- a/cortex-engine/Cargo.toml +++ b/cortex-engine/Cargo.toml @@ -109,22 +109,29 @@ hostname = { workspace = true } flume = "0.11" # Platform specific -[target.'cfg(target_os = "linux")'.dependencies] +# Linux glibc builds - uses linux-native keyring backend (libsecret/D-Bus) +[target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies] +landlock = { workspace = true } +seccompiler = { workspace = true } +libc = { workspace = true } +keyring = { workspace = true, features = ["linux-native"] } + +# Linux musl builds (static) - uses linux-keyutils backend (no D-Bus dependency) +[target.'cfg(all(target_os = "linux", target_env = "musl"))'.dependencies] landlock = { workspace = true } seccompiler = { workspace = true } libc = { workspace = true } -# Linux uses linux-native keyring backend for better integration keyring = { workspace = true, features = ["linux-native"] } [target.'cfg(target_os = "macos")'.dependencies] libc = { workspace = true } # macOS uses default keyring backend (Security.framework) -keyring = { workspace = true } +keyring = { workspace = true, features = ["apple-native"] } [target.'cfg(target_os = "windows")'.dependencies] cortex-windows-sandbox = { path = "../cortex-windows-sandbox" } # Windows uses default keyring backend (Windows Credential Manager) -keyring = { workspace = true } +keyring = { workspace = true, features = ["windows-native"] } [target.'cfg(unix)'.dependencies] libc = { workspace = true } diff --git a/cortex-keyring-store/Cargo.toml b/cortex-keyring-store/Cargo.toml index 23bb81c4..58fb5943 100644 --- a/cortex-keyring-store/Cargo.toml +++ b/cortex-keyring-store/Cargo.toml @@ -8,9 +8,27 @@ description = "Keyring-based credential storage for Cortex CLI" [lints] workspace = true +[features] +default = [] +# Use linux-native backend (libsecret/D-Bus) - for glibc builds +linux-native = ["keyring/linux-native"] + [dependencies] -keyring = { workspace = true, features = ["linux-native"] } tracing = { workspace = true } thiserror = { workspace = true } +# Keyring with conditional features based on target environment +# For musl builds, we use linux-keyutils which doesn't require D-Bus +[target.'cfg(all(target_os = "linux", target_env = "musl"))'.dependencies] +keyring = { workspace = true, features = ["linux-native"] } + +[target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies] +keyring = { workspace = true, features = ["linux-native"] } + +[target.'cfg(target_os = "macos")'.dependencies] +keyring = { workspace = true, features = ["apple-native"] } + +[target.'cfg(target_os = "windows")'.dependencies] +keyring = { workspace = true, features = ["windows-native"] } + [dev-dependencies] diff --git a/cortex-login/Cargo.toml b/cortex-login/Cargo.toml index d894f2fd..18ef4b25 100644 --- a/cortex-login/Cargo.toml +++ b/cortex-login/Cargo.toml @@ -26,7 +26,6 @@ uuid = { workspace = true } chrono = { workspace = true } dirs = { workspace = true } hostname = "0.4" -keyring = { workspace = true, features = ["linux-native", "windows-native", "apple-native"] } urlencoding = { workspace = true } rand = { workspace = true } @@ -35,6 +34,19 @@ secrecy = { version = "0.10", features = ["serde"] } aes-gcm = "0.10" zeroize = { version = "1.8", features = ["derive"] } +# Keyring with platform-specific backends +[target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies] +keyring = { workspace = true, features = ["linux-native"] } + +[target.'cfg(all(target_os = "linux", target_env = "musl"))'.dependencies] +keyring = { workspace = true, features = ["linux-native"] } + +[target.'cfg(target_os = "macos")'.dependencies] +keyring = { workspace = true, features = ["apple-native"] } + +[target.'cfg(target_os = "windows")'.dependencies] +keyring = { workspace = true, features = ["windows-native"] } + [target.'cfg(unix)'.dependencies] libc = "0.2"