name: Release Dev on: push: branches: [main] workflow_dispatch: permissions: contents: write packages: write defaults: run: shell: bash jobs: # --------------------------------------------------------------------------- # Compute all versions once at the start to avoid git-describe race conditions # --------------------------------------------------------------------------- compute-versions: name: Compute Versions runs-on: build-amd64 timeout-minutes: 5 container: image: ghcr.io/nvidia/openshell/ci:latest credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} outputs: python_version: ${{ steps.v.outputs.python }} cargo_version: ${{ steps.v.outputs.cargo }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Fetch tags run: git fetch --tags --force - name: Compute all versions id: v run: | set -euo pipefail echo "python=$(uv run python tasks/scripts/release.py get-version --python)" >> "$GITHUB_OUTPUT" echo "cargo=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT" build-gateway: needs: [compute-versions] uses: ./.github/workflows/docker-build.yml with: component: gateway cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} build-cluster: needs: [compute-versions] uses: ./.github/workflows/docker-build.yml with: component: cluster cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} tag-ghcr-dev: name: Tag GHCR Images as Dev needs: [build-gateway, build-cluster] runs-on: build-amd64 timeout-minutes: 10 steps: - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - name: Tag images as dev run: | set -euo pipefail REGISTRY="ghcr.io/nvidia/openshell" for component in gateway cluster; do echo "Tagging ${REGISTRY}/${component}:${{ github.sha }} as dev..." docker buildx imagetools create \ --prefer-index=false \ -t "${REGISTRY}/${component}:dev" \ "${REGISTRY}/${component}:${{ github.sha }}" done build-python-wheels: name: Stage Python Wheels needs: [compute-versions, build-gateway, build-cluster] runs-on: build-amd64 timeout-minutes: 120 outputs: wheel_version: ${{ needs.compute-versions.outputs.python_version }} s3_prefix: ${{ steps.upload.outputs.s3_prefix }} container: image: ghcr.io/nvidia/openshell/ci:latest credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} options: --privileged volumes: - /var/run/docker.sock:/var/run/docker.sock env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} OPENSHELL_IMAGE_TAG: dev NAV_PYPI_S3_BUCKET: navigator-pypi-artifacts # TODO: rename bucket to openshell-pypi-artifacts AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-2 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - name: Set up Docker Buildx uses: ./.github/actions/setup-buildx - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Sync Python dependencies run: uv sync - name: Build Python wheels run: | set -euo pipefail OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:multiarch OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:macos ls -la target/wheels/*.whl - name: Upload wheels to S3 id: upload run: | set -euo pipefail WHEEL_VERSION="${{ needs.compute-versions.outputs.python_version }}" S3_PREFIX="openshell/${WHEEL_VERSION}" aws s3 cp target/wheels/ "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" --recursive --exclude "*" --include "*.whl" aws s3 ls "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" echo "s3_prefix=${S3_PREFIX}" >> "$GITHUB_OUTPUT" publish-python: name: Publish Python needs: [build-python-wheels] runs-on: [self-hosted, nv] timeout-minutes: 10 env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NAV_PYPI_S3_BUCKET: navigator-pypi-artifacts # TODO: rename bucket to openshell-pypi-artifacts NAV_PYPI_REPOSITORY_URL: https://urm.nvidia.com/artifactory/api/pypi/nv-shared-pypi-local NAV_PYPI_USERNAME: ${{ secrets.NAV_PYPI_USERNAME }} NAV_PYPI_PASSWORD: ${{ secrets.NAV_PYPI_PASSWORD }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-west-2 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install publish dependencies run: | set -euo pipefail python -m pip install --upgrade pip uv if ! command -v aws >/dev/null 2>&1; then ARCH="$(uname -m)" case "$ARCH" in x86_64|amd64) AWSCLI_ARCH="x86_64" ;; aarch64|arm64) AWSCLI_ARCH="aarch64" ;; *) echo "Unsupported architecture for AWS CLI installer: $ARCH" >&2 exit 1 ;; esac rm -rf aws awscliv2.zip curl --fail --silent --show-error --location \ "https://awscli.amazonaws.com/awscli-exe-linux-${AWSCLI_ARCH}.zip" \ --output awscliv2.zip unzip -q awscliv2.zip ./aws/install --install-dir "$HOME/.local/aws-cli" --bin-dir "$HOME/.local/bin" --update echo "$HOME/.local/bin" >> "$GITHUB_PATH" export PATH="$HOME/.local/bin:$PATH" fi aws --version uv --version - name: List and download versioned wheels from S3 run: | set -euo pipefail WHEEL_VERSION="${{ needs.build-python-wheels.outputs.wheel_version }}" S3_PREFIX="${{ needs.build-python-wheels.outputs.s3_prefix }}" OBJECT_COUNT=$(aws s3api list-objects-v2 --bucket "$NAV_PYPI_S3_BUCKET" --prefix "${S3_PREFIX}/" --query "length(Contents)" --output text) if [ "$OBJECT_COUNT" = "None" ] || [ "$OBJECT_COUNT" = "0" ]; then echo "No wheel artifacts found for ${WHEEL_VERSION} at s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" >&2 exit 1 fi aws s3api list-objects-v2 --bucket "$NAV_PYPI_S3_BUCKET" --prefix "${S3_PREFIX}/" --query "Contents[].Key" --output text mkdir -p target/wheels aws s3 cp "s3://${NAV_PYPI_S3_BUCKET}/${S3_PREFIX}/" target/wheels/ --recursive --exclude "*" --include "*.whl" ls -la target/wheels/*.whl - name: Publish wheels to Artifactory run: | set -euo pipefail WHEEL_VERSION="${{ needs.build-python-wheels.outputs.wheel_version }}" uv run python tasks/scripts/release.py python-publish --version "$WHEEL_VERSION" # --------------------------------------------------------------------------- # Build CLI binaries (Linux musl — static, native on each arch) # --------------------------------------------------------------------------- build-cli-linux: name: Build CLI (Linux ${{ matrix.arch }}) needs: [compute-versions] strategy: matrix: include: - arch: amd64 runner: build-amd64 target: x86_64-unknown-linux-musl - arch: arm64 runner: build-arm64 target: aarch64-unknown-linux-musl runs-on: ${{ matrix.runner }} timeout-minutes: 60 container: image: ghcr.io/nvidia/openshell/ci:latest credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} options: --privileged env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} OPENSHELL_IMAGE_TAG: dev steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Fetch tags run: git fetch --tags --force - name: Install tools run: mise install - name: Cache Rust target and registry uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 with: shared-key: cli-musl-${{ matrix.arch }} cache-directories: .cache/sccache cache-targets: "true" - name: Install musl toolchain run: | set -euo pipefail apt-get update apt-get install -y --no-install-recommends musl-tools rm -rf /var/lib/apt/lists/* - name: Add Rust musl target run: mise x -- rustup target add ${{ matrix.target }} - name: Scope workspace to CLI crates run: | set -euo pipefail # Remove workspace members that are not needed for openshell-cli. # This avoids Cargo feature-unification pulling in aws-lc-sys (via # russh in openshell-sandbox / openshell-server). sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-cli", "crates/openshell-core", "crates/openshell-bootstrap", "crates/openshell-policy", "crates/openshell-providers", "crates/openshell-tui"]|' Cargo.toml - name: Patch workspace version if: needs.compute-versions.outputs.cargo_version != '' run: | 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 }} run: mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli - name: sccache stats if: always() run: mise x -- sccache --show-stats - name: Package binary run: | set -euo pipefail mkdir -p artifacts tar -czf artifacts/openshell-${{ matrix.target }}.tar.gz \ -C target/${{ matrix.target }}/release openshell ls -lh artifacts/ - name: Upload artifact uses: actions/upload-artifact@v4 with: name: cli-linux-${{ matrix.arch }} path: artifacts/*.tar.gz retention-days: 5 # --------------------------------------------------------------------------- # Build CLI binary (macOS aarch64 via osxcross) # --------------------------------------------------------------------------- build-cli-macos: name: Build CLI (macOS) needs: [compute-versions] runs-on: build-amd64 timeout-minutes: 60 container: image: ghcr.io/nvidia/openshell/ci:latest credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} options: --privileged volumes: - /var/run/docker.sock:/var/run/docker.sock env: MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SCCACHE_MEMCACHED_ENDPOINT: ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Mark workspace safe for git run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Fetch tags run: git fetch --tags --force - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - name: Set up Docker Buildx uses: ./.github/actions/setup-buildx - name: Build macOS binary via Docker run: | set -euo pipefail docker buildx build \ --file deploy/docker/Dockerfile.cli-macos \ --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ --build-arg OPENSHELL_IMAGE_TAG=dev \ --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ --target binary \ --output type=local,dest=out/ \ . - name: Package binary run: | set -euo pipefail mkdir -p artifacts tar -czf artifacts/openshell-aarch64-apple-darwin.tar.gz \ -C out openshell ls -lh artifacts/ - name: Upload artifact uses: actions/upload-artifact@v4 with: name: cli-macos path: artifacts/*.tar.gz retention-days: 5 # --------------------------------------------------------------------------- # Create / update the devel GitHub Release with CLI binaries # --------------------------------------------------------------------------- release-devel: name: Release Devel needs: [build-cli-linux, build-cli-macos] runs-on: build-amd64 timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Download all CLI artifacts uses: actions/download-artifact@v4 with: pattern: cli-* path: release/ merge-multiple: true - name: Generate checksums run: | set -euo pipefail cd release sha256sum *.tar.gz > openshell-checksums-sha256.txt cat openshell-checksums-sha256.txt - name: Move devel tag run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag -fa devel -m "Latest Devel" "${GITHUB_SHA}" git push --force origin devel - name: Create / update GitHub Release uses: softprops/action-gh-release@v2 with: name: OpenShell Development Build prerelease: true tag_name: devel target_commitish: ${{ github.sha }} body: | This build is automatically built on every commit to main that passes CI. > **NOTE**: This is a development build, not a tagged release, and may be unstable. ### Quick install Requires the [GitHub CLI (`gh`)](https://cli.github.com) to be installed and authenticated. ```bash sh -c 'ARCH=$(uname -m); OS=$(uname -s); \ case "${OS}-${ARCH}" in \ Linux-x86_64) ASSET="openshell-x86_64-unknown-linux-musl.tar.gz" ;; \ Linux-aarch64) ASSET="openshell-aarch64-unknown-linux-musl.tar.gz" ;; \ Darwin-arm64) ASSET="openshell-aarch64-apple-darwin.tar.gz" ;; \ *) echo "Unsupported platform: ${OS}-${ARCH}" >&2; exit 1 ;; \ esac; \ gh release download devel --repo NVIDIA/OpenShell --pattern "${ASSET}" -O - \ | tar xz \ && sudo install -m 755 openshell /usr/local/bin/openshell' ``` ### Assets | File | Platform | Install | |------|----------|---------| | `openshell-x86_64-unknown-linux-musl.tar.gz` | Linux x86_64 | `gh release download devel --repo NVIDIA/OpenShell --pattern "openshell-x86_64-unknown-linux-musl.tar.gz" -O - \| tar xz && sudo install -m 755 openshell /usr/local/bin/openshell` | | `openshell-aarch64-unknown-linux-musl.tar.gz` | Linux aarch64 / ARM64 | `gh release download devel --repo NVIDIA/OpenShell --pattern "openshell-aarch64-unknown-linux-musl.tar.gz" -O - \| tar xz && sudo install -m 755 openshell /usr/local/bin/openshell` | | `openshell-aarch64-apple-darwin.tar.gz` | macOS Apple Silicon | `gh release download devel --repo NVIDIA/OpenShell --pattern "openshell-aarch64-apple-darwin.tar.gz" -O - \| tar xz && sudo install -m 755 openshell /usr/local/bin/openshell` | | `openshell-checksums-sha256.txt` | — | SHA256 checksums for all archives | files: | release/openshell-x86_64-unknown-linux-musl.tar.gz release/openshell-aarch64-unknown-linux-musl.tar.gz release/openshell-aarch64-apple-darwin.tar.gz release/openshell-checksums-sha256.txt