diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index 54084fddd..349068d70 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -10,7 +10,6 @@ env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SCCACHE_GHA_ENABLED: "true" permissions: contents: read @@ -88,6 +87,7 @@ jobs: runner: [linux-amd64-cpu8, linux-arm64-cpu8] runs-on: ${{ matrix.runner }} env: + SCCACHE_GHA_ENABLED: "true" SCCACHE_GHA_VERSION: branch-checks-rust-${{ matrix.runner }} container: image: ghcr.io/nvidia/openshell/ci:latest @@ -97,12 +97,12 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Install tools - run: mise install --locked - - name: Configure GHA sccache backend uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 + - name: Install tools + run: mise install --locked + - name: Cache Rust target and registry uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 with: diff --git a/.github/workflows/driver-vm-linux.yml b/.github/workflows/driver-vm-linux.yml index 3ee804257..6d42217c3 100644 --- a/.github/workflows/driver-vm-linux.yml +++ b/.github/workflows/driver-vm-linux.yml @@ -81,11 +81,13 @@ jobs: - arch: arm64 runner: linux-arm64-cpu8 target: aarch64-unknown-linux-gnu + zig_target: aarch64-unknown-linux-gnu.2.31 platform: linux-aarch64 guest_arch: aarch64 - arch: amd64 runner: linux-amd64-cpu8 target: x86_64-unknown-linux-gnu + zig_target: x86_64-unknown-linux-gnu.2.31 platform: linux-x86_64 guest_arch: x86_64 runs-on: ${{ matrix.runner }} @@ -163,19 +165,25 @@ jobs: set -euo pipefail sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ inputs['cargo-version'] }}"'"/}' Cargo.toml - - name: Build openshell-driver-vm + - name: Build openshell-driver-vm with glibc 2.31 floor run: | set -euo pipefail + mise x -- rustup target add ${{ matrix.target }} OPENSHELL_VM_RUNTIME_COMPRESSED_DIR="${PWD}/target/vm-runtime-compressed" \ - mise x -- cargo build --release -p openshell-driver-vm + mise x -- cargo zigbuild --release --target ${{ matrix.zig_target }} -p openshell-driver-vm --bin openshell-driver-vm + mkdir -p artifacts/bin + install -m 0755 target/${{ matrix.target }}/release/openshell-driver-vm artifacts/bin/openshell-driver-vm - name: Verify packaged binary run: | set -euo pipefail - OUTPUT="$(target/release/openshell-driver-vm --version)" + OUTPUT="$(artifacts/bin/openshell-driver-vm --version)" echo "$OUTPUT" grep -q '^openshell-driver-vm ' <<<"$OUTPUT" + - name: Verify glibc symbol floor + run: tasks/scripts/verify-glibc-symbols.sh 2.31 artifacts/bin/openshell-driver-vm + - name: sccache stats if: always() run: mise x -- sccache --show-stats @@ -185,7 +193,7 @@ jobs: set -euo pipefail mkdir -p artifacts tar -czf "artifacts/openshell-driver-vm-${{ matrix.target }}.tar.gz" \ - -C target/release openshell-driver-vm + -C artifacts/bin openshell-driver-vm - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 1114a2b74..5c8eac435 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -376,7 +376,7 @@ jobs: retention-days: 5 # --------------------------------------------------------------------------- - # Build standalone gateway binaries (Linux GNU — native on each arch) + # Build standalone gateway binaries (Linux GNU — glibc 2.31 floor) # --------------------------------------------------------------------------- build-gateway-binary-linux: name: Build Gateway Binary (Linux ${{ matrix.arch }}) @@ -387,9 +387,11 @@ jobs: - arch: amd64 runner: linux-amd64-cpu8 target: x86_64-unknown-linux-gnu + zig_target: x86_64-unknown-linux-gnu.2.31 - arch: arm64 runner: linux-arm64-cpu8 target: aarch64-unknown-linux-gnu + zig_target: aarch64-unknown-linux-gnu.2.31 runs-on: ${{ matrix.runner }} timeout-minutes: 60 container: @@ -427,20 +429,26 @@ jobs: set -euo pipefail sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - name: Build ${{ matrix.target }} + - name: Build ${{ matrix.zig_target }} env: OPENSHELL_IMAGE_TAG: ${{ github.sha }} run: | set -euo pipefail - mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-server + mise x -- rustup target add ${{ matrix.target }} + mise x -- cargo zigbuild --release --target ${{ matrix.zig_target }} -p openshell-server --bin openshell-gateway + mkdir -p artifacts/bin + install -m 0755 target/${{ matrix.target }}/release/openshell-gateway artifacts/bin/openshell-gateway - name: Verify packaged binary run: | set -euo pipefail - OUTPUT="$(target/${{ matrix.target }}/release/openshell-gateway --version)" + OUTPUT="$(artifacts/bin/openshell-gateway --version)" echo "$OUTPUT" grep -q '^openshell-gateway ' <<<"$OUTPUT" + - name: Verify glibc symbol floor + run: tasks/scripts/verify-glibc-symbols.sh 2.31 artifacts/bin/openshell-gateway + - name: sccache stats if: always() run: mise x -- sccache --show-stats @@ -450,7 +458,7 @@ jobs: set -euo pipefail mkdir -p artifacts tar -czf artifacts/openshell-gateway-${{ matrix.target }}.tar.gz \ - -C target/${{ matrix.target }}/release openshell-gateway + -C artifacts/bin openshell-gateway ls -lh artifacts/ - name: Upload artifact @@ -641,7 +649,7 @@ jobs: build-rpm: name: Build RPM Packages - needs: [compute-versions] + needs: [compute-versions, build-cli-linux, build-gateway-binary-linux] uses: ./.github/workflows/rpm-package.yml with: checkout-ref: ${{ github.sha }} @@ -650,12 +658,46 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} secrets: inherit + smoke-linux-dev-artifacts: + name: Smoke Linux Dev Artifacts (${{ matrix.name }}) + needs: [build-gateway-binary-linux, build-driver-vm-linux, build-deb] + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - name: ubuntu-22.04-deb-amd64 + runner: linux-amd64-cpu8 + image: ubuntu:22.04 + artifact_arch: amd64 + - name: ubuntu-22.04-deb-arm64 + runner: linux-arm64-cpu8 + image: ubuntu:22.04 + artifact_arch: arm64 + runs-on: ${{ matrix.runner }} + container: + image: ${{ matrix.image }} + steps: + - name: Download Debian package artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: deb-linux-${{ matrix.artifact_arch }} + path: package-input/ + + - name: Smoke Debian package on Ubuntu 22.04 + run: | + set -euo pipefail + apt-get update + apt-get install -y --no-install-recommends ./package-input/*.deb + openshell-gateway --version + /usr/libexec/openshell/openshell-driver-vm --version + # --------------------------------------------------------------------------- # Create / update the dev GitHub Release with CLI, gateway, driver, and wheels # --------------------------------------------------------------------------- release-dev: name: Release Dev - needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, build-driver-vm-linux, build-driver-vm-macos, build-deb, build-rpm] + needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, build-driver-vm-linux, build-driver-vm-macos, build-deb, build-rpm, smoke-linux-dev-artifacts] runs-on: linux-amd64-cpu8 timeout-minutes: 10 permissions: diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index ae842494b..b2e27f4df 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -409,7 +409,7 @@ jobs: retention-days: 5 # --------------------------------------------------------------------------- - # Build standalone gateway binaries (Linux GNU — native on each arch) + # Build standalone gateway binaries (Linux GNU — glibc 2.31 floor) # --------------------------------------------------------------------------- build-gateway-binary-linux: name: Build Gateway Binary (Linux ${{ matrix.arch }}) @@ -420,9 +420,11 @@ jobs: - arch: amd64 runner: linux-amd64-cpu8 target: x86_64-unknown-linux-gnu + zig_target: x86_64-unknown-linux-gnu.2.31 - arch: arm64 runner: linux-arm64-cpu8 target: aarch64-unknown-linux-gnu + zig_target: aarch64-unknown-linux-gnu.2.31 runs-on: ${{ matrix.runner }} timeout-minutes: 60 container: @@ -461,20 +463,26 @@ jobs: set -euo pipefail sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - name: Build ${{ matrix.target }} + - name: Build ${{ matrix.zig_target }} env: OPENSHELL_IMAGE_TAG: ${{ needs.compute-versions.outputs.source_sha }} run: | set -euo pipefail - mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-server + mise x -- rustup target add ${{ matrix.target }} + mise x -- cargo zigbuild --release --target ${{ matrix.zig_target }} -p openshell-server --bin openshell-gateway + mkdir -p artifacts/bin + install -m 0755 target/${{ matrix.target }}/release/openshell-gateway artifacts/bin/openshell-gateway - name: Verify packaged binary run: | set -euo pipefail - OUTPUT="$(target/${{ matrix.target }}/release/openshell-gateway --version)" + OUTPUT="$(artifacts/bin/openshell-gateway --version)" echo "$OUTPUT" grep -q '^openshell-gateway ' <<<"$OUTPUT" + - name: Verify glibc symbol floor + run: tasks/scripts/verify-glibc-symbols.sh 2.31 artifacts/bin/openshell-gateway + - name: sccache stats if: always() run: mise x -- sccache --show-stats @@ -484,7 +492,7 @@ jobs: set -euo pipefail mkdir -p artifacts tar -czf artifacts/openshell-gateway-${{ matrix.target }}.tar.gz \ - -C target/${{ matrix.target }}/release openshell-gateway + -C artifacts/bin openshell-gateway ls -lh artifacts/ - name: Upload artifact @@ -677,7 +685,7 @@ jobs: build-rpm: name: Build RPM Packages - needs: [compute-versions] + needs: [compute-versions, build-cli-linux, build-gateway-binary-linux] uses: ./.github/workflows/rpm-package.yml with: checkout-ref: ${{ inputs.tag || github.ref }} @@ -686,12 +694,120 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} secrets: inherit + smoke-linux-release-artifacts: + name: Smoke Linux Release Artifacts (${{ matrix.name }}) + needs: [build-gateway-binary-linux, build-driver-vm-linux, build-deb, build-rpm] + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - name: ubuntu-20.04-binaries + runner: linux-amd64-cpu8 + image: ubuntu:20.04 + kind: binary + artifact_arch: amd64 + rpm_arch: x86_64 + target: x86_64-unknown-linux-gnu + - name: ubuntu-20.04-binaries-arm64 + runner: linux-arm64-cpu8 + image: ubuntu:20.04 + kind: binary + artifact_arch: arm64 + rpm_arch: aarch64 + target: aarch64-unknown-linux-gnu + - name: ubuntu-22.04-deb + runner: linux-amd64-cpu8 + image: ubuntu:22.04 + kind: deb + artifact_arch: amd64 + rpm_arch: x86_64 + target: x86_64-unknown-linux-gnu + - name: ubuntu-22.04-deb-arm64 + runner: linux-arm64-cpu8 + image: ubuntu:22.04 + kind: deb + artifact_arch: arm64 + rpm_arch: aarch64 + target: aarch64-unknown-linux-gnu + - name: rhel9-rpm + runner: linux-amd64-cpu8 + image: rockylinux:9 + kind: rpm + artifact_arch: amd64 + rpm_arch: x86_64 + target: x86_64-unknown-linux-gnu + - name: rhel9-rpm-aarch64 + runner: linux-arm64-cpu8 + image: rockylinux:9 + kind: rpm + artifact_arch: arm64 + rpm_arch: aarch64 + target: aarch64-unknown-linux-gnu + runs-on: ${{ matrix.runner }} + container: + image: ${{ matrix.image }} + steps: + - name: Download gateway binary artifact + if: matrix.kind == 'binary' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: gateway-binary-linux-${{ matrix.artifact_arch }} + path: smoke-input/ + + - name: Download VM driver binary artifact + if: matrix.kind == 'binary' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: driver-vm-linux-${{ matrix.artifact_arch }} + path: smoke-input/ + + - name: Smoke binary artifacts + if: matrix.kind == 'binary' + run: | + set -euo pipefail + mkdir -p smoke-bin + tar -xzf smoke-input/openshell-gateway-${{ matrix.target }}.tar.gz -C smoke-bin + tar -xzf smoke-input/openshell-driver-vm-${{ matrix.target }}.tar.gz -C smoke-bin + smoke-bin/openshell-gateway --version + smoke-bin/openshell-driver-vm --version + + - name: Download Debian package artifact + if: matrix.kind == 'deb' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: deb-linux-${{ matrix.artifact_arch }} + path: package-input/ + + - name: Smoke Debian package + if: matrix.kind == 'deb' + run: | + set -euo pipefail + apt-get update + apt-get install -y --no-install-recommends ./package-input/*.deb + openshell-gateway --version + /usr/libexec/openshell/openshell-driver-vm --version + + - name: Download RPM package artifacts + if: matrix.kind == 'rpm' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: rpm-linux-${{ matrix.rpm_arch }} + path: package-input/ + + - name: Smoke RPM packages + if: matrix.kind == 'rpm' + run: | + set -euo pipefail + dnf install -y ./package-input/*.rpm + openshell-gateway --version + # --------------------------------------------------------------------------- # Create a tagged GitHub Release with CLI, gateway, driver, and wheels # --------------------------------------------------------------------------- release: name: Release - needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, tag-ghcr-release, build-driver-vm-linux, build-driver-vm-macos, build-deb, build-rpm] + needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheels-linux, build-python-wheel-macos, tag-ghcr-release, build-driver-vm-linux, build-driver-vm-macos, build-deb, build-rpm, smoke-linux-release-artifacts] runs-on: linux-amd64-cpu8 timeout-minutes: 10 permissions: diff --git a/.github/workflows/rpm-package.yml b/.github/workflows/rpm-package.yml index e96b19958..078563c4e 100644 --- a/.github/workflows/rpm-package.yml +++ b/.github/workflows/rpm-package.yml @@ -37,9 +37,15 @@ jobs: matrix: include: - arch: x86_64 + artifact_arch: amd64 runner: linux-amd64-cpu8 + cli_target: x86_64-unknown-linux-musl + gnu_target: x86_64-unknown-linux-gnu - arch: aarch64 + artifact_arch: arm64 runner: linux-arm64-cpu8 + cli_target: aarch64-unknown-linux-musl + gnu_target: aarch64-unknown-linux-gnu runs-on: ${{ matrix.runner }} timeout-minutes: 60 container: @@ -59,6 +65,26 @@ jobs: ref: ${{ inputs.checkout-ref }} fetch-depth: 0 + - name: Download CLI artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: cli-linux-${{ matrix.artifact_arch }} + path: package-input/ + + - name: Download gateway artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: gateway-binary-linux-${{ matrix.artifact_arch }} + path: package-input/ + + - name: Extract package inputs + run: | + set -euo pipefail + mkdir -p package-binaries + tar -xzf "package-input/openshell-${{ matrix.cli_target }}.tar.gz" -C package-binaries + tar -xzf "package-input/openshell-gateway-${{ matrix.gnu_target }}.tar.gz" -C package-binaries + ls -lah package-binaries + - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" @@ -70,6 +96,7 @@ jobs: OPENSHELL_RPM_VERSION: ${{ inputs['rpm-version'] }} OPENSHELL_RPM_RELEASE: ${{ inputs['rpm-release'] }} OPENSHELL_CARGO_VERSION: ${{ inputs['cargo-version'] }} + OPENSHELL_PREBUILT_BINARIES_DIR: ${{ github.workspace }}/package-binaries run: packit build locally - name: Collect RPM artifacts diff --git a/.github/workflows/rust-native-build.yml b/.github/workflows/rust-native-build.yml index 1086ee5e8..682d5eb88 100644 --- a/.github/workflows/rust-native-build.yml +++ b/.github/workflows/rust-native-build.yml @@ -6,7 +6,9 @@ name: Rust Image Binary Build (openshell-gateway / openshell-sandbox) # Build Rust binaries per Linux architecture before the Docker image build # consumes them as prebuilt artifacts. Gateway images use GNU-linked binaries # for the NVIDIA distroless C/C++ runtime; supervisor images use musl/static -# binaries so the final image can remain scratch. +# binaries so the final image can remain scratch. Gateway GNU binaries are +# built with an explicit glibc 2.31 floor so image, package, and tarball +# artifacts share the same host portability contract. on: workflow_call: @@ -95,6 +97,11 @@ jobs: - name: Fetch tags run: git fetch --tags --force + - name: Configure GHA sccache backend + # Exposes ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN before `mise install` + # compiles cargo-installed tools through RUSTC_WRAPPER=sccache. + uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 + - name: Install tools run: mise install --locked @@ -127,6 +134,7 @@ jobs: zig_target=x86_64-linux-musl else target=x86_64-unknown-linux-gnu + zig_target=x86_64-unknown-linux-gnu.2.31 fi ;; arm64) @@ -135,6 +143,7 @@ jobs: zig_target=aarch64-linux-musl else target=aarch64-unknown-linux-gnu + zig_target=aarch64-unknown-linux-gnu.2.31 fi ;; *) @@ -150,11 +159,6 @@ jobs: echo "zig_target=$zig_target" } >> "$GITHUB_OUTPUT" - - name: Configure GHA sccache backend - # Exposes ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN so sccache (wrapped - # around rustc via mise's RUSTC_WRAPPER) can initialize the GHA cache. - uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - - name: Cache Rust target and registry uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2 with: @@ -202,7 +206,7 @@ jobs: echo "CARGO_TARGET_${TARGET_ENV_UPPER}_LINKER=/tmp/zig-musl/cc" >> "$GITHUB_ENV" echo "CARGO_TARGET_${TARGET_ENV_UPPER}_RUSTFLAGS=-Clink-self-contained=no" >> "$GITHUB_ENV" - - name: Build ${{ steps.target.outputs.binary }} (${{ steps.target.outputs.target }}) + - name: Build ${{ steps.target.outputs.binary }} (${{ steps.target.outputs.zig_target || steps.target.outputs.target }}) env: # Preserve the release-codegen setting used by the old Dockerfile # Rust build path so image artifacts keep the same release profile. @@ -211,9 +215,15 @@ jobs: run: | set -euo pipefail mise x -- rustup target add "${{ steps.target.outputs.target }}" + cargo_cmd=(cargo build) + build_target="${{ steps.target.outputs.target }}" + if [[ "${{ inputs.component }}" == "gateway" ]]; then + cargo_cmd=(cargo zigbuild) + build_target="${{ steps.target.outputs.zig_target }}" + fi args=( --release - --target "${{ steps.target.outputs.target }}" + --target "$build_target" -p "${{ steps.target.outputs.crate }}" --bin "${{ steps.target.outputs.binary }}" ) @@ -223,7 +233,7 @@ jobs: if [[ -n "${{ steps.version.outputs.cargo_version }}" ]]; then export GIT_DIR=/nonexistent fi - mise x -- cargo build "${args[@]}" + mise x -- "${cargo_cmd[@]}" "${args[@]}" - name: Verify packaged binary run: | @@ -236,6 +246,13 @@ jobs: ldd --version ldd "$BIN" || true + - name: Verify glibc symbol floor + if: inputs.component == 'gateway' + run: | + set -euo pipefail + BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" + tasks/scripts/verify-glibc-symbols.sh 2.31 "$BIN" + - name: Stage binary for prebuilt layout run: | set -euo pipefail diff --git a/architecture/build.md b/architecture/build.md index be50eeb8d..200be8b1e 100644 --- a/architecture/build.md +++ b/architecture/build.md @@ -20,6 +20,15 @@ OpenShell builds these main artifacts: Sandbox community images are built outside this repository. +## Linux Runtime Environments + +OpenShell uses different Linux libc environments for different host artifacts. +The standalone `openshell` CLI is built as a static musl binary so it can run on +a wide range of Linux distributions without depending on the host's glibc. Host +runtime binaries that use the GNU/Linux runtime environment, including +`openshell-gateway` and `openshell-driver-vm`, are GNU-linked and built with a +glibc 2.31 floor. + ## Container Builds The Docker image pipeline is a two-step flow: build the Rust binary natively @@ -29,9 +38,11 @@ and the supervisor image from `deploy/docker/Dockerfile.supervisor`. Neither Dockerfile compiles Rust — both copy a staged binary out of `deploy/docker/.build/prebuilt-binaries//` into the final image. -Binary staging is driven by `tasks/scripts/stage-prebuilt-binaries.sh`, which -runs `cargo build` natively on a matching host or `cargo zigbuild` when -cross-compiling. Local Docker image tasks infer the target architecture from +Binary staging is driven by `tasks/scripts/stage-prebuilt-binaries.sh`. Gateway +binaries use `cargo zigbuild` with GNU targets pinned to glibc 2.31, including +native-architecture builds, so the gateway image, standalone tarballs, and Linux +packages share the same host portability floor. Supervisor binaries remain +static musl. Local Docker image tasks infer the target architecture from `DOCKER_PLATFORM` when set, otherwise from the container engine host metadata with the kernel architecture as the fallback. CI invokes the same staging step via the `rust-native-build.yml` workflow (per-architecture, per-component) and @@ -41,7 +52,9 @@ the staging directory before running Buildx. Runtime layout: - **Gateway**: `gcr.io/distroless/cc-debian13:nonroot` base, GNU-linked binary at - `/usr/local/bin/openshell-gateway`, runs as UID/GID `1000:1000`. + `/usr/local/bin/openshell-gateway`, runs as UID/GID `1000:1000`. Linux GNU + gateway and VM driver binaries must not reference `GLIBC_*` symbols newer than + `GLIBC_2.31`; release workflows verify this before publishing artifacts. - **Supervisor**: `scratch` base, static musl binary at `/openshell-sandbox`. Static linkage is required because the image is mounted/extracted into sandbox environments (Docker extraction, Podman image volumes, Kubernetes diff --git a/docs/about/installation.mdx b/docs/about/installation.mdx index 1675e6d2f..cd9973f13 100644 --- a/docs/about/installation.mdx +++ b/docs/about/installation.mdx @@ -55,6 +55,8 @@ On Fedora and RHEL, the install script uses RPM packages. The RPM installs the ` On Debian and Ubuntu, the install script uses a Debian package. The Debian package installs the `openshell` CLI, the `openshell-gateway` daemon, VM sandbox support, and a systemd user service. +Linux packages require glibc 2.31 or newer. The installer checks libc before downloading packages and exits with an error on older glibc versions, Alpine, musl-based distributions, or unknown libc environments. + The Linux user service listens on `https://127.0.0.1:17670`, starts from built-in defaults, and generates a local mTLS bundle before the gateway starts. Create `~/.config/openshell/gateway.toml` only when you need to override those defaults. The CLI reads the client bundle from `~/.config/openshell/gateways/openshell/mtls/`. diff --git a/docs/reference/support-matrix.mdx b/docs/reference/support-matrix.mdx index 9bcda85e5..f278b15eb 100644 --- a/docs/reference/support-matrix.mdx +++ b/docs/reference/support-matrix.mdx @@ -10,7 +10,7 @@ This page lists the host platform, compute driver, software, runtime, and kernel ## Supported Platforms -OpenShell publishes multi-architecture gateway images for `linux/amd64` and `linux/arm64`. The CLI and standalone gateway binary are supported on the following host platforms: +OpenShell publishes multi-architecture gateway images for `linux/amd64` and `linux/arm64`. The CLI, package-managed gateway, and standalone gateway binary are supported on the following host platforms: | Platform | Architecture | Status | | -------------------------------- | --------------------- | --------- | @@ -19,6 +19,8 @@ OpenShell publishes multi-architecture gateway images for `linux/amd64` and `lin | macOS (Docker Desktop) | Apple Silicon (arm64) | Supported | | Windows (WSL 2 + Docker Desktop) | x86_64 | Experimental | +On Linux, the `openshell` CLI is a static musl binary and does not require glibc at runtime. + ## Standalone Gateway Binary OpenShell publishes standalone `openshell-gateway` release assets for manual download on these platforms: @@ -31,6 +33,8 @@ OpenShell publishes standalone `openshell-gateway` release assets for manual dow These artifacts are attached to GitHub releases. Kubernetes deployments should use the Helm chart and the published gateway image. +On Linux, `openshell-gateway` requires glibc 2.31 or newer. Compatible systems include, for example, Ubuntu 20.04+, RHEL 9+, Amazon Linux 2023+, and Fedora 32+. + ## Compute Drivers The gateway can manage sandboxes through several compute drivers. diff --git a/install.sh b/install.sh index ce81ab5a2..6a8bc3029 100755 --- a/install.sh +++ b/install.sh @@ -19,6 +19,7 @@ LOCAL_GATEWAY_PORT="17670" HOMEBREW_TAP="nvidia/openshell" HOMEBREW_FORMULA_NAME="openshell" BREAKING_RELEASE_VERSION="0.0.37" +LINUX_PACKAGE_GLIBC_MIN_VERSION="2.31" UPGRADE_NOTICE_ACK="${OPENSHELL_ACK_BREAKING_UPGRADE:-}" info() { @@ -118,6 +119,104 @@ semver_at_least() { [ "$_patch" -ge "$_min_patch" ] } +version_at_least_major_minor() { + _version="$1" + _minimum="$2" + + _major="${_version%%.*}" + _minor="${_version#*.}" + _minor="${_minor%%.*}" + + _min_major="${_minimum%%.*}" + _min_minor="${_minimum#*.}" + _min_minor="${_min_minor%%.*}" + + case "$_major:$_minor:$_min_major:$_min_minor" in + *[!0-9:]* | *::*) + return 1 + ;; + esac + + [ "$_major" -gt "$_min_major" ] && return 0 + [ "$_major" -lt "$_min_major" ] && return 1 + [ "$_minor" -ge "$_min_minor" ] +} + +getconf_gnu_libc_version() { + if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_GETCONF_UNAVAILABLE:-0}" = "1" ]; then + return 127 + fi + + if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_GETCONF_OUTPUT+x}" = "x" ]; then + printf '%s\n' "$OPENSHELL_TEST_GETCONF_OUTPUT" + return 0 + fi + + getconf GNU_LIBC_VERSION 2>/dev/null +} + +ldd_version_output() { + if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_LDD_UNAVAILABLE:-0}" = "1" ]; then + return 127 + fi + + if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_LDD_OUTPUT+x}" = "x" ]; then + printf '%s\n' "$OPENSHELL_TEST_LDD_OUTPUT" + return 0 + fi + + ldd --version 2>&1 +} + +detect_glibc_version() { + _ldd_output="$(ldd_version_output 2>&1 || true)" + case "$_ldd_output" in + *[Mm][Uu][Ss][Ll]* | *[Aa][Ll][Pp][Ii][Nn][Ee]*) + return 2 + ;; + esac + + _ldd_version="$(printf '%s\n' "$_ldd_output" | awk 'FNR == 1 && match($NF, /^[0-9]+\.[0-9]+/) { print substr($NF, RSTART, RLENGTH); exit }')" + if [ -n "$_ldd_version" ]; then + printf '%s\n' "$_ldd_version" + return 0 + fi + + _getconf_output="$(getconf_gnu_libc_version 2>/dev/null || true)" + case "$_getconf_output" in + glibc\ [0-9]*.[0-9]*) + printf '%s\n' "${_getconf_output#glibc }" + return 0 + ;; + *[Mm][Uu][Ss][Ll]* | *[Aa][Ll][Pp][Ii][Nn][Ee]*) + return 2 + ;; + esac + + return 1 +} + +require_linux_package_glibc() { + _glibc_status=0 + _glibc_version="$(detect_glibc_version)" || _glibc_status=$? + + case "$_glibc_status" in + 0) + ;; + 2) + error "OpenShell Linux packages require glibc >= ${LINUX_PACKAGE_GLIBC_MIN_VERSION}; detected musl or unsupported libc." + ;; + *) + error "OpenShell Linux packages require glibc >= ${LINUX_PACKAGE_GLIBC_MIN_VERSION}; could not detect glibc." + ;; + esac + + if ! version_at_least_major_minor "$_glibc_version" "$LINUX_PACKAGE_GLIBC_MIN_VERSION"; then + error "OpenShell Linux packages require glibc >= ${LINUX_PACKAGE_GLIBC_MIN_VERSION}; detected glibc ${_glibc_version}. +Please use a newer distribution or container environment." + fi +} + target_uses_breaking_gateway_model() { case "$RELEASE_TAG" in dev) @@ -934,6 +1033,7 @@ main() { case "$PLATFORM" in linux) + require_linux_package_glibc case "$(linux_package_method)" in deb) install_linux_deb @@ -955,4 +1055,6 @@ main() { esac } -main "$@" +if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" != "1" ]; then + main "$@" +fi diff --git a/mise.toml b/mise.toml index f3aa2cb9d..90fa87480 100644 --- a/mise.toml +++ b/mise.toml @@ -35,7 +35,7 @@ k3d = { version = "5.8.3", os = ["macos"] } "github:anchore/syft" = { version = "1.44.0" } "github:EmbarkStudios/cargo-about" = { version = "0.8.4", version_prefix = "" } zig = "0.14.1" -"cargo:cargo-zigbuild" = { version = "0.22.3", os = ["macos"] } +"cargo:cargo-zigbuild" = "0.22.3" "npm:markdownlint-cli2" = "0.22.0" [tools."github:mozilla/sccache"] diff --git a/openshell.spec b/openshell.spec index 1f3e9cd97..edcb77870 100644 --- a/openshell.spec +++ b/openshell.spec @@ -103,12 +103,18 @@ sed -i 's/^version = "0.0.0"/version = "%{openshell_cargo_version}"/' Cargo.toml grep -q 'version = "%{openshell_cargo_version}"' Cargo.toml || (echo "ERROR: Cargo.toml version patch failed" && exit 1) %build -# Build the CLI and gateway binaries +# Build the CLI and gateway binaries unless the release workflow supplied the +# same prebuilt artifacts used for tarballs and Debian packages. export CARGO_BUILD_JOBS=%{_smp_build_ncpus} # Set the default container image tag so compiled-in image refs point at # real tags in the ghcr.io/nvidia/openshell registry. export OPENSHELL_IMAGE_TAG=%{image_tag} -cargo build --release --bin openshell --bin openshell-gateway +if [ -n "${OPENSHELL_PREBUILT_BINARIES_DIR:-}" ]; then + test -x "${OPENSHELL_PREBUILT_BINARIES_DIR}/openshell" + test -x "${OPENSHELL_PREBUILT_BINARIES_DIR}/openshell-gateway" +else + cargo build --release --bin openshell --bin openshell-gateway +fi # Generate vendored crate manifest and license metadata. # cargo-vendor.txt is consumed by an RPM generator (from cargo-rpm-macros) @@ -123,10 +129,18 @@ pandoc -s -t man deploy/man/openshell-gateway.8.md -o openshell-gateway.8 %install # --- CLI binary --- -install -Dpm 0755 target/release/%{name} %{buildroot}%{_bindir}/%{name} +if [ -n "${OPENSHELL_PREBUILT_BINARIES_DIR:-}" ]; then + install -Dpm 0755 "${OPENSHELL_PREBUILT_BINARIES_DIR}/%{name}" %{buildroot}%{_bindir}/%{name} +else + install -Dpm 0755 target/release/%{name} %{buildroot}%{_bindir}/%{name} +fi # --- Gateway binary --- -install -Dpm 0755 target/release/%{name}-gateway %{buildroot}%{_bindir}/%{name}-gateway +if [ -n "${OPENSHELL_PREBUILT_BINARIES_DIR:-}" ]; then + install -Dpm 0755 "${OPENSHELL_PREBUILT_BINARIES_DIR}/%{name}-gateway" %{buildroot}%{_bindir}/%{name}-gateway +else + install -Dpm 0755 target/release/%{name}-gateway %{buildroot}%{_bindir}/%{name}-gateway +fi # --- Default gateway TOML config template --- # Shipped as a read-only reference in %{_datadir}. The systemd unit seeds a diff --git a/tasks/scripts/stage-prebuilt-binaries.sh b/tasks/scripts/stage-prebuilt-binaries.sh index 7c20d53a0..271bc8025 100755 --- a/tasks/scripts/stage-prebuilt-binaries.sh +++ b/tasks/scripts/stage-prebuilt-binaries.sh @@ -141,6 +141,7 @@ build_component_for_arch() { local stage local features local cargo_subcommand + local build_target local current_host_os local current_host_arch @@ -152,7 +153,17 @@ build_component_for_arch() { current_host_arch="$(host_arch)" cargo_subcommand=(cargo build) - if [[ "$current_host_os" != "Linux" || "$current_host_arch" != "$arch" ]]; then + build_target="$target" + + if [[ "$component" == "gateway" ]]; then + if command -v cargo-zigbuild >/dev/null 2>&1 || mise which cargo-zigbuild >/dev/null 2>&1; then + cargo_subcommand=(cargo zigbuild) + build_target="${target}.2.31" + else + echo "Error: cargo-zigbuild + zig are required to build ${binary} with the glibc 2.31 floor." >&2 + exit 1 + fi + elif [[ "$current_host_os" != "Linux" || "$current_host_arch" != "$arch" ]]; then if command -v cargo-zigbuild >/dev/null 2>&1 || mise which cargo-zigbuild >/dev/null 2>&1; then cargo_subcommand=(cargo zigbuild) else @@ -163,12 +174,12 @@ build_component_for_arch() { fi fi - echo "Building ${binary} for linux/${arch} (${target})..." + echo "Building ${binary} for linux/${arch} (${build_target})..." mise x -- rustup target add "$target" >/dev/null 2>&1 || true args=( --release - --target "$target" + --target "$build_target" -p "$crate" --bin "$binary" ) diff --git a/tasks/scripts/test-install-sh.sh b/tasks/scripts/test-install-sh.sh new file mode 100755 index 000000000..5bf98071a --- /dev/null +++ b/tasks/scripts/test-install-sh.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT +out="${tmpdir}/out" +err="${tmpdir}/err" + +export OPENSHELL_INSTALL_SH_TEST=1 +# shellcheck source=../../install.sh +. "${ROOT}/install.sh" + +assert_glibc_preflight_passes() { + local name=$1 + local ldd_output=$2 + + if ! (export OPENSHELL_TEST_GETCONF_UNAVAILABLE=1 OPENSHELL_TEST_LDD_OUTPUT="$ldd_output"; require_linux_package_glibc) >"$out" 2>"$err"; then + echo "FAIL: ${name}" >&2 + cat "$err" >&2 || true + exit 1 + fi +} + +assert_glibc_preflight_fails() { + local name=$1 + local expected=$2 + local setup=$3 + + if ("$setup"; require_linux_package_glibc) >"$out" 2>"$err"; then + echo "FAIL: ${name}: expected failure" >&2 + exit 1 + fi + + if ! grep -Fq "$expected" "$err"; then + echo "FAIL: ${name}: missing expected message" >&2 + echo "Expected: ${expected}" >&2 + echo "Actual:" >&2 + cat "$err" >&2 || true + exit 1 + fi +} + +setup_glibc_228() { + export OPENSHELL_TEST_GETCONF_UNAVAILABLE=1 + export OPENSHELL_TEST_LDD_OUTPUT="ldd (GNU libc) 2.28" +} + +setup_missing_glibc() { + export OPENSHELL_TEST_GETCONF_UNAVAILABLE=1 + export OPENSHELL_TEST_LDD_UNAVAILABLE=1 +} + +setup_getconf_musl() { + export OPENSHELL_TEST_LDD_UNAVAILABLE=1 + export OPENSHELL_TEST_GETCONF_OUTPUT="musl libc" +} + +setup_ldd_musl() { + export OPENSHELL_TEST_GETCONF_UNAVAILABLE=1 + export OPENSHELL_TEST_LDD_OUTPUT="musl libc (x86_64)" +} + +assert_glibc_preflight_passes "glibc 2.31 passes" "glibc 2.31" +assert_glibc_preflight_passes "glibc 2.35 passes" "ldd (GNU libc) 2.35" + +if ! (export OPENSHELL_TEST_LDD_UNAVAILABLE=1 OPENSHELL_TEST_GETCONF_OUTPUT="glibc 2.35"; require_linux_package_glibc) >"$out" 2>"$err"; then + echo "FAIL: getconf glibc fallback passes" >&2 + cat "$err" >&2 || true + exit 1 +fi + +if ! (export OPENSHELL_TEST_LDD_OUTPUT="not ldd" OPENSHELL_TEST_GETCONF_OUTPUT="glibc 2.35"; require_linux_package_glibc) >"$out" 2>"$err"; then + echo "FAIL: unparseable ldd output falls back to getconf" >&2 + cat "$err" >&2 || true + exit 1 +fi + +assert_glibc_preflight_fails \ + "glibc 2.28 fails" \ + "OpenShell Linux packages require glibc >= 2.31; detected glibc 2.28." \ + setup_glibc_228 + +assert_glibc_preflight_fails \ + "missing glibc detection fails" \ + "OpenShell Linux packages require glibc >= 2.31; could not detect glibc." \ + setup_missing_glibc + +assert_glibc_preflight_fails \ + "musl detection fails" \ + "OpenShell Linux packages require glibc >= 2.31; detected musl or unsupported libc." \ + setup_getconf_musl + +assert_glibc_preflight_fails \ + "ldd musl fallback fails" \ + "OpenShell Linux packages require glibc >= 2.31; detected musl or unsupported libc." \ + setup_ldd_musl + +echo "install.sh libc preflight tests passed" diff --git a/tasks/scripts/verify-glibc-symbols.sh b/tasks/scripts/verify-glibc-symbols.sh new file mode 100755 index 000000000..21d24b788 --- /dev/null +++ b/tasks/scripts/verify-glibc-symbols.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +usage() { + echo "Usage: verify-glibc-symbols.sh [binary ...]" >&2 +} + +version_gt() { + local left=$1 + local right=$2 + local left_major=${left%%.*} + local left_minor=${left#*.} + local right_major=${right%%.*} + local right_minor=${right#*.} + + left_minor=${left_minor%%.*} + right_minor=${right_minor%%.*} + + [[ $left_major =~ ^[0-9]+$ && $left_minor =~ ^[0-9]+$ ]] || return 1 + [[ $right_major =~ ^[0-9]+$ && $right_minor =~ ^[0-9]+$ ]] || return 1 + + (( left_major > right_major )) && return 0 + (( left_major < right_major )) && return 1 + (( left_minor > right_minor )) +} + +extract_glibc_versions() { + local binary=$1 + + if command -v readelf >/dev/null 2>&1; then + readelf --version-info "$binary" 2>/dev/null | grep -oE 'GLIBC_[0-9]+\.[0-9]+' || true + elif command -v objdump >/dev/null 2>&1; then + objdump -T "$binary" 2>/dev/null | grep -oE 'GLIBC_[0-9]+\.[0-9]+' || true + fi +} + +if [[ $# -lt 2 ]]; then + usage + exit 2 +fi + +max_version=$1 +shift +failed=0 + +if ! command -v readelf >/dev/null 2>&1 && ! command -v objdump >/dev/null 2>&1; then + echo "error: readelf or objdump is required to inspect GLIBC symbol versions" >&2 + exit 2 +fi + +for binary in "$@"; do + if [[ ! -f $binary ]]; then + echo "error: binary not found: $binary" >&2 + failed=1 + continue + fi + + echo "==> Inspecting $binary" + if command -v file >/dev/null 2>&1; then + file "$binary" || true + else + echo "file: not available" + fi + + if command -v ldd >/dev/null 2>&1; then + ldd "$binary" || true + else + echo "ldd: not available" + fi + + highest= + found=0 + while IFS= read -r version; do + version=${version#GLIBC_} + found=1 + + if [[ -z $highest ]] || version_gt "$version" "$highest"; then + highest=$version + fi + + if version_gt "$version" "$max_version"; then + echo "error: $binary references GLIBC_${version}, above allowed GLIBC_${max_version}" >&2 + failed=1 + fi + done < <(extract_glibc_versions "$binary") + + if [[ $found -eq 0 ]]; then + echo "highest GLIBC symbol: none" + continue + fi + + echo "highest GLIBC symbol: GLIBC_${highest}" +done + +exit "$failed" diff --git a/tasks/test.toml b/tasks/test.toml index c6ac82180..b829a4f62 100644 --- a/tasks/test.toml +++ b/tasks/test.toml @@ -5,7 +5,12 @@ [test] description = "Run all tests (Rust + Python)" -depends = ["test:rust", "test:python"] +depends = ["test:rust", "test:python", "test:install-sh"] + +["test:install-sh"] +description = "Run focused install.sh shell tests" +run = "tasks/scripts/test-install-sh.sh" +hide = true [e2e] description = "Run all end-to-end tests (Rust + Python)"