From bc5870581498b0134505c9cf9ef59223d7445954 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:20:21 -0700 Subject: [PATCH 01/10] chore(installer): promote package install script --- .github/workflows/release-canary.yml | 6 +- .github/workflows/test-install.yml | 8 +- crates/openshell-driver-vm/README.md | 6 +- e2e/install/bash_test.sh | 6 +- e2e/install/fish_test.fish | 10 +- e2e/install/helpers.sh | 10 +- e2e/install/sh_test.sh | 6 +- e2e/install/zsh_test.sh | 6 +- install-dev.sh | 714 ----------------------- install.sh | 822 ++++++++++++++++++++------- 10 files changed, 653 insertions(+), 941 deletions(-) delete mode 100755 install-dev.sh diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 5e895efc7..bd1192ec4 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Install dev and check status run: | - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install-dev.sh | sh + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh openshell status ubuntu: @@ -44,7 +44,7 @@ jobs: - name: Install dev and check status run: | - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install-dev.sh | sh + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh openshell status fedora: @@ -65,5 +65,5 @@ jobs: - name: Install dev and check status run: | - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install-dev.sh | sh + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh openshell status diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml index 06b1e007f..9a5b43767 100644 --- a/.github/workflows/test-install.yml +++ b/.github/workflows/test-install.yml @@ -1,15 +1,15 @@ -name: Test Install Script +name: Test Legacy Install Script on: pull_request: paths: - - 'install.sh' + - 'install-legacy.sh' - 'e2e/install/**' - '.github/workflows/test-install.yml' push: branches: [main] paths: - - 'install.sh' + - 'install-legacy.sh' - 'e2e/install/**' - '.github/workflows/test-install.yml' workflow_dispatch: @@ -19,7 +19,7 @@ permissions: jobs: test-install: - name: install.sh (${{ matrix.shell }}) + name: install-legacy.sh (${{ matrix.shell }}) runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index 046554c59..49f2ef005 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -180,17 +180,17 @@ The VM guest's serial console is appended to `//console.l - runtime tarballs: the rolling `vm-runtime` release, rebuilt on demand by `release-vm-kernel.yml` -On Debian-family Linux amd64 and arm64 systems, `install-dev.sh` installs the +On Debian-family Linux amd64 and arm64 systems, `install.sh` installs the Debian package from the selected `OPENSHELL_VERSION` release tag. That package includes `openshell-gateway` and `openshell-driver-vm`, but leaves `OPENSHELL_DRIVERS` unset so the gateway uses its normal runtime auto-detection. Set `OPENSHELL_DRIVERS=vm` to force the VM driver. -On RPM-family Linux x86_64 and aarch64 systems, `install-dev.sh` installs the +On RPM-family Linux x86_64 and aarch64 systems, `install.sh` installs the `openshell` and `openshell-gateway` RPM packages from the selected release tag. The RPM gateway package is configured for the Podman driver. -On Apple Silicon macOS, `install-dev.sh` stages the generated `openshell.rb` +On Apple Silicon macOS, `install.sh` stages the generated `openshell.rb` formula from the selected release in the `nvidia/openshell` Homebrew tap. Homebrew installs `openshell`, `openshell-gateway`, and `openshell-driver-vm`, ad-hoc signs the driver with the Hypervisor entitlement diff --git a/e2e/install/bash_test.sh b/e2e/install/bash_test.sh index 2b4db1caf..7546f3a5f 100755 --- a/e2e/install/bash_test.sh +++ b/e2e/install/bash_test.sh @@ -2,9 +2,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# Bash e2e tests for install.sh. +# Bash e2e tests for install-legacy.sh. # -# Downloads the latest release for real and validates: +# Downloads the latest legacy tarball release for real and validates: # - Binary is installed to the correct directory # - Binary is executable and runs # - PATH guidance shows the correct export command for bash @@ -65,7 +65,7 @@ test_guidance_mentions_not_on_path() { # Runner # --------------------------------------------------------------------------- -printf '=== install.sh e2e tests: bash ===\n\n' +printf '=== install-legacy.sh e2e tests: bash ===\n\n' printf 'Installing openshell...\n' SHELL="/bin/bash" run_install diff --git a/e2e/install/fish_test.fish b/e2e/install/fish_test.fish index 101760715..46c831b46 100755 --- a/e2e/install/fish_test.fish +++ b/e2e/install/fish_test.fish @@ -2,9 +2,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# Fish e2e tests for install.sh. +# Fish e2e tests for install-legacy.sh. # -# Downloads the latest release for real and validates: +# Downloads the latest legacy tarball release for real and validates: # - Binary is installed to the correct directory # - Binary is executable and runs # - PATH guidance shows fish_add_path (not export PATH) @@ -15,7 +15,7 @@ set -g FAIL 0 # Resolve paths relative to this script set -g SCRIPT_DIR (builtin cd (dirname (status filename)) && pwd) set -g REPO_ROOT (builtin cd "$SCRIPT_DIR/../.." && pwd) -set -g INSTALL_SCRIPT "$REPO_ROOT/install.sh" +set -g INSTALL_SCRIPT "$REPO_ROOT/install-legacy.sh" # Set by run_install set -g INSTALL_DIR "" @@ -71,7 +71,7 @@ function run_install sh "$INSTALL_SCRIPT" 2>&1) if test $status -ne 0 - printf 'install.sh failed:\n%s\n' "$INSTALL_OUTPUT" >&2 + printf 'install-legacy.sh failed:\n%s\n' "$INSTALL_OUTPUT" >&2 return 1 end end @@ -129,7 +129,7 @@ end # Runner # --------------------------------------------------------------------------- -printf '=== install.sh e2e tests: fish ===\n\n' +printf '=== install-legacy.sh e2e tests: fish ===\n\n' printf 'Installing openshell...\n' run_install diff --git a/e2e/install/helpers.sh b/e2e/install/helpers.sh index ff5f66376..afc6a2bcc 100644 --- a/e2e/install/helpers.sh +++ b/e2e/install/helpers.sh @@ -2,19 +2,19 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# Shared test helpers for install.sh e2e tests. +# Shared test helpers for install-legacy.sh e2e tests. # Sourced by each per-shell test file (except fish, which has its own helpers). # # Provides: # - pass / fail / print_summary # - assert_output_contains / assert_output_not_contains -# - run_install (runs the real install.sh to a temp dir, captures output) +# - run_install (runs install-legacy.sh to a temp dir, captures output) # - REPO_ROOT / INSTALL_SCRIPT paths # - INSTALL_DIR / INSTALL_OUTPUT (set after run_install) HELPERS_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$HELPERS_DIR/../.." && pwd)" -INSTALL_SCRIPT="$REPO_ROOT/install.sh" +INSTALL_SCRIPT="$REPO_ROOT/install-legacy.sh" _PASS=0 _FAIL=0 @@ -68,7 +68,7 @@ assert_output_not_contains() { # Install runner # --------------------------------------------------------------------------- -# Run the real install.sh, installing to a temp directory with the install +# Run install-legacy.sh, installing to a temp directory with the install # dir removed from PATH so we always get PATH guidance output. # # Sets INSTALL_DIR and INSTALL_OUTPUT for subsequent assertions. @@ -85,7 +85,7 @@ run_install() { INSTALL_OUTPUT="$(OPENSHELL_INSTALL_DIR="$INSTALL_DIR" \ PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \ sh "$INSTALL_SCRIPT" 2>&1)" || { - printf 'install.sh failed:\n%s\n' "$INSTALL_OUTPUT" >&2 + printf 'install-legacy.sh failed:\n%s\n' "$INSTALL_OUTPUT" >&2 return 1 } } diff --git a/e2e/install/sh_test.sh b/e2e/install/sh_test.sh index 320c00efb..38a9dc7d8 100755 --- a/e2e/install/sh_test.sh +++ b/e2e/install/sh_test.sh @@ -2,9 +2,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# POSIX sh e2e tests for install.sh. +# POSIX sh e2e tests for install-legacy.sh. # -# Downloads the latest release for real and validates: +# Downloads the latest legacy tarball release for real and validates: # - Binary is installed to the correct directory # - Binary is executable and runs # - PATH guidance shows the correct export command for sh @@ -88,7 +88,7 @@ test_no_env_scripts_created() { # Runner # --------------------------------------------------------------------------- -printf '=== install.sh e2e tests: sh ===\n\n' +printf '=== install-legacy.sh e2e tests: sh ===\n\n' printf 'Installing openshell...\n' SHELL="/bin/sh" run_install diff --git a/e2e/install/zsh_test.sh b/e2e/install/zsh_test.sh index 621d35f8e..1531bba15 100755 --- a/e2e/install/zsh_test.sh +++ b/e2e/install/zsh_test.sh @@ -2,9 +2,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# Zsh e2e tests for install.sh. +# Zsh e2e tests for install-legacy.sh. # -# Downloads the latest release for real and validates: +# Downloads the latest legacy tarball release for real and validates: # - Binary is installed to the correct directory # - Binary is executable and runs # - PATH guidance shows the correct export command for zsh @@ -65,7 +65,7 @@ test_guidance_mentions_not_on_path() { # Runner # --------------------------------------------------------------------------- -printf '=== install.sh e2e tests: zsh ===\n\n' +printf '=== install-legacy.sh e2e tests: zsh ===\n\n' printf 'Installing openshell...\n' SHELL="/bin/zsh" run_install diff --git a/install-dev.sh b/install-dev.sh deleted file mode 100755 index e48152862..000000000 --- a/install-dev.sh +++ /dev/null @@ -1,714 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Install the OpenShell development build from a GitHub release. -# -# Linux installs either the Debian or RPM packages from the selected release. -# Apple Silicon macOS installs the generated Homebrew formula, so Homebrew owns -# the binary layout and launchd service lifecycle. -# -set -e - -APP_NAME="openshell" -REPO="NVIDIA/OpenShell" -GITHUB_URL="https://github.com/${REPO}" -RELEASE_TAG="${OPENSHELL_VERSION:-dev}" -CHECKSUMS_NAME="openshell-checksums-sha256.txt" -LOCAL_GATEWAY_PORT="17670" -HOMEBREW_TAP="nvidia/openshell" -HOMEBREW_FORMULA_NAME="openshell" - -info() { - printf '%s: %s\n' "$APP_NAME" "$*" >&2 -} - -warn() { - printf '%s: warning: %s\n' "$APP_NAME" "$*" >&2 -} - -error() { - printf '%s: error: %s\n' "$APP_NAME" "$*" >&2 - exit 1 -} - -usage() { - cat </dev/null 2>&1 -} - -require_cmd() { - if ! has_cmd "$1"; then - error "'$1' is required" - fi -} - -download() { - _url="$1" - _output="$2" - curl -fLsS --retry 3 --max-redirs 5 -o "$_output" "$_url" -} - -download_release_asset() { - _tag="$1" - _filename="$2" - _output="$3" - - if curl -fLs --retry 3 --max-redirs 5 -o "$_output" \ - "${GITHUB_URL}/releases/download/${_tag}/${_filename}"; then - return 0 - fi - - # GitHub normalizes `~` to `.` in release asset names, while checksum files - # can still record package filenames with `~dev` for correct version ordering. - # Download the normalized asset but verify it against the checksum entry for - # the original package filename. - _normalized="$(printf '%s' "$_filename" | tr '~' '.')" - if [ "$_normalized" != "$_filename" ]; then - if download "${GITHUB_URL}/releases/download/${_tag}/${_normalized}" "$_output"; then - info "using GitHub-normalized asset name ${_normalized}" - return 0 - fi - fi - - return 1 -} - -as_root() { - if [ "$(id -u)" -eq 0 ]; then - "$@" - elif has_cmd sudo; then - sudo "$@" - else - error "this installer needs root privileges; rerun as root or install sudo" - fi -} - -target_user() { - if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then - echo "$SUDO_USER" - else - id -un - fi -} - -user_home() { - _user="$1" - if has_cmd getent; then - _home="$(getent passwd "$_user" | awk -F: '{ print $6 }')" - if [ -n "$_home" ]; then - echo "$_home" - return 0 - fi - fi - - if [ "$(uname -s)" = "Darwin" ] && has_cmd dscl; then - _home="$(dscl . -read "/Users/${_user}" NFSHomeDirectory 2>/dev/null | awk '{ print $2 }')" - if [ -n "$_home" ]; then - echo "$_home" - return 0 - fi - fi - - if [ "$(id -un)" = "$_user" ]; then - echo "${HOME:-}" - return 0 - fi - - if [ "$(uname -s)" = "Darwin" ]; then - echo "/Users/${_user}" - return 0 - fi - - echo "/home/${_user}" -} - -as_target_user() { - if [ "${PLATFORM:-}" = "darwin" ]; then - if [ "$(id -u)" -eq "$TARGET_UID" ]; then - env HOME="$TARGET_HOME" "$@" - elif has_cmd sudo; then - sudo -u "$TARGET_USER" env HOME="$TARGET_HOME" "$@" - else - error "cannot run commands as ${TARGET_USER}; install sudo or run as ${TARGET_USER}" - fi - return - fi - - _bus="unix:path=${TARGET_RUNTIME_DIR}/bus" - if [ "$(id -u)" -eq "$TARGET_UID" ]; then - env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" - elif has_cmd sudo; then - sudo -u "$TARGET_USER" env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" - elif has_cmd runuser; then - runuser -u "$TARGET_USER" -- env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" - else - error "cannot run user service commands as ${TARGET_USER}; install sudo or run as ${TARGET_USER}" - fi -} - -detect_platform() { - case "$(uname -s)" in - Linux) - echo "linux" - ;; - Darwin) - echo "darwin" - ;; - *) - error "unsupported OS: $(uname -s); dev builds support Linux and macOS" - ;; - esac -} - -linux_package_method() { - if has_cmd dpkg; then - echo "deb" - elif has_cmd rpm; then - echo "rpm" - else - error "Linux dev installs require either dpkg or rpm" - fi -} - -set_linux_target_runtime_dir() { - if [ "$(id -u)" -eq "$TARGET_UID" ] && [ -n "${XDG_RUNTIME_DIR:-}" ]; then - TARGET_RUNTIME_DIR="$XDG_RUNTIME_DIR" - else - TARGET_RUNTIME_DIR="/run/user/${TARGET_UID}" - fi -} - -check_linux_deb_platform() { - require_cmd dpkg -} - -check_macos_platform() { - _arch="$(uname -m)" - - case "$_arch" in - arm64|aarch64) - ;; - x86_64|amd64) - error "Intel macOS is not supported because no x86_64-apple-darwin dev assets are published" - ;; - *) - error "no macOS dev build is published for architecture: ${_arch}" - ;; - esac - - if ! as_target_user brew --version >/dev/null 2>&1; then - error "Homebrew is required for macOS dev installs; install it from https://brew.sh" - fi -} - -get_deb_arch() { - _arch="$(dpkg --print-architecture)" - - case "$_arch" in - amd64|arm64) - echo "$_arch" - ;; - *) - error "no dev Debian package is published for architecture: ${_arch}" - ;; - esac -} - -get_rpm_arch() { - if has_cmd rpm; then - _arch="$(rpm --eval '%{_arch}' 2>/dev/null || true)" - else - _arch="" - fi - - if [ -z "$_arch" ]; then - _arch="$(uname -m)" - fi - - case "$_arch" in - x86_64|amd64) - echo "x86_64" - ;; - aarch64|arm64) - echo "aarch64" - ;; - *) - error "no dev RPM package is published for architecture: ${_arch}" - ;; - esac -} - -find_deb_asset() { - _checksums="$1" - _arch="$2" - - awk -v arch="$_arch" ' - $2 ~ "^\\*?openshell[-_].*[-_]" arch "\\.deb$" { - sub("^\\*", "", $2) - print $2 - exit - } - ' "$_checksums" -} - -find_rpm_asset() { - _checksums="$1" - _arch="$2" - _package="$3" - - case "$_package" in - openshell) - _dev_name="openshell-dev-${_arch}.rpm" - _fallback_re="^openshell-[0-9].*\\.${_arch}\\.rpm$" - ;; - openshell-gateway) - _dev_name="openshell-gateway-dev-${_arch}.rpm" - _fallback_re="^openshell-gateway-[0-9].*\\.${_arch}\\.rpm$" - ;; - *) - error "unknown RPM package selector: ${_package}" - ;; - esac - - awk -v dev_name="$_dev_name" -v fallback_re="$_fallback_re" ' - { - name = $2 - sub("^\\*", "", name) - - if (name == dev_name) { - selected = name - found = 1 - exit - } - - if (fallback == "" && name ~ fallback_re) { - fallback = name - } - } - END { - if (found) { - print selected - } else if (fallback != "") { - print fallback - } - } - ' "$_checksums" -} - -verify_checksum() { - _archive="$1" - _checksums="$2" - _filename="$3" - - if has_cmd sha256sum; then - _expected="$(awk -v name="$_filename" '($2 == name || $2 == "*" name) { print $1; exit }' "$_checksums")" - [ -n "$_expected" ] || error "no checksum entry found for ${_filename}" - echo "$_expected $_archive" | sha256sum -c --quiet - elif has_cmd shasum; then - _expected="$(awk -v name="$_filename" '($2 == name || $2 == "*" name) { print $1; exit }' "$_checksums")" - [ -n "$_expected" ] || error "no checksum entry found for ${_filename}" - echo "$_expected $_archive" | shasum -a 256 -c --quiet - else - error "neither 'sha256sum' nor 'shasum' found; cannot verify download integrity" - fi -} - -install_deb_package() { - _deb_path="$1" - - if has_cmd apt-get; then - as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y \ - -o Dpkg::Options::=--force-confdef \ - -o Dpkg::Options::=--force-confnew \ - "$_deb_path" - elif has_cmd apt; then - as_root env DEBIAN_FRONTEND=noninteractive apt install -y \ - -o Dpkg::Options::=--force-confdef \ - -o Dpkg::Options::=--force-confnew \ - "$_deb_path" - else - as_root dpkg --force-confdef --force-confnew -i "$_deb_path" - fi -} - -install_rpm_packages() { - if has_cmd dnf; then - as_root dnf install -y "$@" - elif has_cmd yum; then - as_root yum install -y "$@" - elif has_cmd zypper; then - as_root zypper --non-interactive install --allow-unsigned-rpm "$@" - elif has_cmd rpm; then - warn "installing with rpm directly; dependencies must already be installed" - as_root rpm -Uvh --replacepkgs "$@" - else - error "'dnf', 'yum', 'zypper', or 'rpm' is required to install RPM packages" - fi -} - -homebrew_formula_path() { - _tap="$1" - _formula="$2" - - if ! as_target_user brew tap-info "$_tap" >/dev/null 2>&1; then - info "creating local Homebrew tap ${_tap}..." - as_target_user brew tap-new --no-git "$_tap" >/dev/null - fi - - _tap_dir="$(as_target_user brew --repository "$_tap" 2>/dev/null || true)" - [ -n "$_tap_dir" ] || error "could not locate Homebrew tap ${_tap}" - - _formula_dir="${_tap_dir}/Formula" - as_target_user mkdir -p "$_formula_dir" - printf '%s/%s.rb\n' "$_formula_dir" "$_formula" -} - -patch_homebrew_formula() { - _formula_file="$1" - _patched_file="${_formula_file}.patched" - - if grep -q 'entitlements.write <<~XML' "$_formula_file"; then - info "patching Homebrew formula for idempotent postinstall..." - sed 's/entitlements\.write <<~XML/entitlements.atomic_write <<~XML/' "$_formula_file" >"$_patched_file" - mv "$_patched_file" "$_formula_file" - fi - -} - -start_user_gateway() { - info "restarting openshell-gateway user service as ${TARGET_USER}..." - - if ! as_target_user systemctl --user daemon-reload; then - info "could not reach the user systemd manager for ${TARGET_USER}" - info "restart the gateway later with: systemctl --user enable openshell-gateway && systemctl --user restart openshell-gateway" - info "then register it with: openshell gateway add https://127.0.0.1:17670 --local --name openshell" - return 0 - fi - - as_target_user systemctl --user enable openshell-gateway - as_target_user systemctl --user restart openshell-gateway - as_target_user systemctl --user is-active --quiet openshell-gateway - - info "registering local gateway as ${TARGET_USER}..." - register_local_gateway - wait_for_local_gateway_listener - wait_for_local_gateway_status -} - -wait_for_local_gateway_listener() { - _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" - _elapsed=0 - _last_output="" - _probe_url="https://127.0.0.1:${LOCAL_GATEWAY_PORT}/" - _mtls_dir="${TARGET_HOME}/.config/openshell/gateways/openshell/mtls" - - info "waiting for local gateway listener to become reachable..." - while [ "$_elapsed" -lt "$_timeout" ]; do - if [ ! -f "${_mtls_dir}/ca.crt" ] || [ ! -f "${_mtls_dir}/tls.crt" ] || [ ! -f "${_mtls_dir}/tls.key" ]; then - _last_output="mTLS client bundle is not ready under ${_mtls_dir}" - elif _last_output="$(as_target_user curl -sS --max-time 2 --cacert "${_mtls_dir}/ca.crt" --cert "${_mtls_dir}/tls.crt" --key "${_mtls_dir}/tls.key" -o /dev/null "$_probe_url" 2>&1)"; then - info "local gateway listener is reachable" - return 0 - fi - sleep 1 - _elapsed=$((_elapsed + 1)) - done - - printf '%s\n' "$_last_output" >&2 - error "local gateway listener did not become reachable at ${_probe_url} within ${_timeout}s" -} - -wait_for_local_gateway_status() { - _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" - _elapsed=0 - _status_output="" - _register_bin="${OPENSHELL_REGISTER_BIN:-openshell}" - - info "waiting for openshell status to report connected..." - while [ "$_elapsed" -lt "$_timeout" ]; do - if _status_output="$(as_target_user env NO_COLOR=1 "$_register_bin" status 2>&1)"; then - case "$_status_output" in - *"Version:"*) - info "openshell status reports connected" - return 0 - ;; - esac - fi - sleep 1 - _elapsed=$((_elapsed + 1)) - done - - printf '%s\n' "$_status_output" >&2 - error "openshell status did not report connected within ${_timeout}s" -} - -remove_local_gateway_registration() { - [ -n "$TARGET_HOME" ] || error "cannot resolve home directory for ${TARGET_USER}" - _config_dir="${TARGET_HOME}/.config/openshell" - - # The install-dev gateway is a user service. Replace the CLI registration - # directly instead of asking `gateway destroy` to tear down Docker resources. - # shellcheck disable=SC2016 - as_target_user sh -c ' - config_dir=$1 - rm -rf "${config_dir}/gateways/local" - mkdir -p "${config_dir}/gateways/openshell" - rm -f \ - "${config_dir}/gateways/openshell/metadata.json" \ - "${config_dir}/gateways/openshell/edge_token" \ - "${config_dir}/gateways/openshell/cf_token" \ - "${config_dir}/gateways/openshell/oidc_token.json" - active="${config_dir}/active_gateway" - active_name="$(cat "$active" 2>/dev/null || true)" - if [ "$active_name" = "local" ] || [ "$active_name" = "openshell" ]; then - rm -f "$active" - fi - ' sh "$_config_dir" -} - -register_local_gateway() { - _register_bin="${OPENSHELL_REGISTER_BIN:-openshell}" - - if _add_output="$(as_target_user "$_register_bin" gateway add "https://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name openshell 2>&1)"; then - [ -z "$_add_output" ] || print_gateway_add_output "$_add_output" - return 0 - else - _add_status=$? - fi - - case "$_add_output" in - *"already exists"*) - info "local gateway already exists; removing and re-adding it..." - remove_local_gateway_registration - as_target_user "$_register_bin" gateway add "https://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name openshell - ;; - *) - printf '%s\n' "$_add_output" >&2 - return "$_add_status" - ;; - esac -} - -print_gateway_add_output() { - printf '%s\n' "$1" | while IFS= read -r _line; do - case "$_line" in - *"Gateway is not reachable at https://127.0.0.1:${LOCAL_GATEWAY_PORT}"*) ;; - *"Verify the gateway is running and the endpoint is correct."*) ;; - *) printf '%s\n' "$_line" >&2 ;; - esac - done -} - -install_linux_deb() { - check_linux_deb_platform - set_linux_target_runtime_dir - - _arch="$(get_deb_arch)" - _tmpdir="$(mktemp -d)" - chmod 0755 "$_tmpdir" - trap 'rm -rf "$_tmpdir"' EXIT - - _checksums_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${CHECKSUMS_NAME}" - info "downloading ${RELEASE_TAG} release checksums..." - download "$_checksums_url" "${_tmpdir}/${CHECKSUMS_NAME}" || { - error "failed to download ${_checksums_url}" - } - - _deb_file="$(find_deb_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch")" - if [ -z "$_deb_file" ]; then - error "no dev Debian package found for architecture: ${_arch}" - fi - - _deb_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${_deb_file}" - _deb_path="${_tmpdir}/${_deb_file}" - - info "selected ${_deb_file}" - - info "downloading ${_deb_file}..." - download_release_asset "$RELEASE_TAG" "$_deb_file" "$_deb_path" || { - error "failed to download ${_deb_url}" - } - chmod 0644 "$_deb_path" - - info "verifying checksum..." - verify_checksum "$_deb_path" "${_tmpdir}/${CHECKSUMS_NAME}" "$_deb_file" - - info "installing ${_deb_file}..." - install_deb_package "$_deb_path" - info "installed ${APP_NAME} package from ${RELEASE_TAG}" - start_user_gateway -} - -install_linux_rpm() { - require_cmd rpm - set_linux_target_runtime_dir - - _arch="$(get_rpm_arch)" - _tmpdir="$(mktemp -d)" - chmod 0755 "$_tmpdir" - trap 'rm -rf "$_tmpdir"' EXIT - - _checksums_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${CHECKSUMS_NAME}" - info "downloading ${RELEASE_TAG} release checksums..." - download "$_checksums_url" "${_tmpdir}/${CHECKSUMS_NAME}" || { - error "failed to download ${_checksums_url}" - } - - _rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell)" - if [ -z "$_rpm_file" ]; then - error "no dev openshell RPM package found for architecture: ${_arch}" - fi - - _gateway_rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell-gateway)" - if [ -z "$_gateway_rpm_file" ]; then - error "no dev openshell-gateway RPM package found for architecture: ${_arch}" - fi - - info "selected ${_rpm_file} and ${_gateway_rpm_file}" - - for _package_file in "$_rpm_file" "$_gateway_rpm_file"; do - _package_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${_package_file}" - _package_path="${_tmpdir}/${_package_file}" - - info "downloading ${_package_file}..." - download_release_asset "$RELEASE_TAG" "$_package_file" "$_package_path" || { - error "failed to download ${_package_url}" - } - chmod 0644 "$_package_path" - - info "verifying checksum for ${_package_file}..." - verify_checksum "$_package_path" "${_tmpdir}/${CHECKSUMS_NAME}" "$_package_file" - done - - info "installing ${_rpm_file} and ${_gateway_rpm_file}..." - install_rpm_packages "${_tmpdir}/${_rpm_file}" "${_tmpdir}/${_gateway_rpm_file}" - info "installed ${APP_NAME} RPM packages from ${RELEASE_TAG}" - start_user_gateway -} - -install_macos_homebrew() { - check_macos_platform - - _tmpdir="$(mktemp -d)" - chmod 0755 "$_tmpdir" - trap 'rm -rf "$_tmpdir"' EXIT - - _formula_file="${_tmpdir}/openshell.rb" - _formula_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/openshell.rb" - - info "downloading Homebrew formula from ${_formula_url}..." - download_release_asset "$RELEASE_TAG" "openshell.rb" "$_formula_file" || { - error "failed to download ${_formula_url}; the selected release may not include a Homebrew formula" - } - chmod 0644 "$_formula_file" - patch_homebrew_formula "$_formula_file" - - _tap_formula_file="$(homebrew_formula_path "$HOMEBREW_TAP" "$HOMEBREW_FORMULA_NAME")" - info "staging Homebrew formula in tap ${HOMEBREW_TAP}..." - cp "$_formula_file" "$_tap_formula_file" - chmod 0644 "$_tap_formula_file" - if [ "$(id -u)" -eq 0 ]; then - chown "$TARGET_USER" "$_tap_formula_file" 2>/dev/null || true - fi - - _formula_ref="${HOMEBREW_TAP}/${HOMEBREW_FORMULA_NAME}" - - if as_target_user brew list --formula openshell >/dev/null 2>&1; then - info "reinstalling OpenShell with Homebrew..." - as_target_user brew reinstall --formula "$_formula_ref" - else - info "installing OpenShell with Homebrew..." - as_target_user brew install --formula "$_formula_ref" - fi - - info "restarting OpenShell Homebrew service..." - if ! as_target_user brew services restart "$_formula_ref"; then - warn "could not restart the OpenShell Homebrew service" - info "restart it later with: brew services restart ${_formula_ref}" - info "then register it with: openshell gateway add https://127.0.0.1:${LOCAL_GATEWAY_PORT} --local --name openshell" - return 0 - fi - - _brew_prefix="$(as_target_user brew --prefix 2>/dev/null || true)" - if [ -n "$_brew_prefix" ] && [ -x "${_brew_prefix}/bin/openshell" ]; then - OPENSHELL_REGISTER_BIN="${_brew_prefix}/bin/openshell" - fi - - info "registering local gateway as ${TARGET_USER}..." - register_local_gateway - wait_for_local_gateway_listener - wait_for_local_gateway_status -} - -main() { - if [ "$#" -gt 0 ]; then - case "$1" in - --help) - usage - exit 0 - ;; - *) - error "unknown option: $1" - ;; - esac - fi - - require_cmd curl - PLATFORM="$(detect_platform)" - - TARGET_USER="$(target_user)" - TARGET_UID="$(id -u "$TARGET_USER" 2>/dev/null || true)" - [ -n "$TARGET_UID" ] || error "cannot resolve uid for ${TARGET_USER}" - TARGET_HOME="$(user_home "$TARGET_USER")" - - case "$PLATFORM" in - linux) - case "$(linux_package_method)" in - deb) - install_linux_deb - ;; - rpm) - install_linux_rpm - ;; - *) - error "unsupported Linux package method" - ;; - esac - ;; - darwin) - install_macos_homebrew - ;; - *) - error "unsupported platform: ${PLATFORM}" - ;; - esac -} - -main "$@" diff --git a/install.sh b/install.sh index 0ad6eee63..2cc2525d6 100755 --- a/install.sh +++ b/install.sh @@ -2,27 +2,22 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # -# Install the OpenShell CLI binary. +# Install OpenShell from a GitHub release. # -# Usage: -# curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh +# Linux installs either the Debian or RPM packages from the selected release. +# Apple Silicon macOS installs the generated Homebrew formula, so Homebrew owns +# the binary layout and launchd service lifecycle. # -# Or run directly: -# ./install.sh -# -# Environment variables: -# OPENSHELL_VERSION - Release tag to install (default: latest tagged release) -# OPENSHELL_INSTALL_DIR - Directory to install into (default: ~/.local/bin) -# -set -eu +set -e APP_NAME="openshell" REPO="NVIDIA/OpenShell" GITHUB_URL="https://github.com/${REPO}" - -# --------------------------------------------------------------------------- -# Logging -# --------------------------------------------------------------------------- +RELEASE_TAG="${OPENSHELL_VERSION:-}" +CHECKSUMS_NAME="openshell-checksums-sha256.txt" +LOCAL_GATEWAY_PORT="17670" +HOMEBREW_TAP="nvidia/openshell" +HOMEBREW_FORMULA_NAME="openshell" info() { printf '%s: %s\n' "$APP_NAME" "$*" >&2 @@ -37,284 +32,715 @@ error() { exit 1 } -# --------------------------------------------------------------------------- -# Usage -# --------------------------------------------------------------------------- - usage() { cat </dev/null 2>&1 } -check_downloader() { - if has_cmd curl; then - return 0 - elif has_cmd wget; then - return 0 - else - error "either 'curl' or 'wget' is required to download files" +require_cmd() { + if ! has_cmd "$1"; then + error "'$1' is required" fi } -# Download a URL to a file. Outputs nothing on success. download() { _url="$1" _output="$2" + curl -fLsS --retry 3 --max-redirs 5 -o "$_output" "$_url" +} - if has_cmd curl; then - curl -fLsS --retry 3 --max-redirs 5 -o "$_output" "$_url" - elif has_cmd wget; then - wget -q --tries=3 --max-redirect=5 -O "$_output" "$_url" +resolve_release_tag() { + if [ -n "${OPENSHELL_VERSION:-}" ]; then + echo "$OPENSHELL_VERSION" + return 0 fi + + info "resolving latest version..." + _latest_url="${GITHUB_URL}/releases/latest" + _resolved="$(curl -fLsS -o /dev/null -w '%{url_effective}' "$_latest_url")" || { + error "failed to resolve latest release from ${_latest_url}" + } + + case "$_resolved" in + https://github.com/${REPO}/releases/*) + ;; + *) + error "unexpected redirect target: ${_resolved} (expected https://github.com/${REPO}/releases/...)" + ;; + esac + + _version="${_resolved##*/}" + if [ -z "$_version" ] || [ "$_version" = "latest" ]; then + error "could not determine latest release version (resolved URL: ${_resolved})" + fi + + echo "$_version" } -# Follow a URL and print the final resolved URL (for detecting redirect targets). -resolve_redirect() { - _url="$1" +download_release_asset() { + _tag="$1" + _filename="$2" + _output="$3" - if has_cmd curl; then - curl -fLsS -o /dev/null -w '%{url_effective}' "$_url" - elif has_cmd wget; then - # wget --spider follows redirects; capture the final Location from stderr - wget --spider --max-redirect=10 "$_url" 2>&1 | sed -n 's/^.*Location: \([^ ]*\).*/\1/p' | tail -1 + if curl -fLs --retry 3 --max-redirs 5 -o "$_output" \ + "${GITHUB_URL}/releases/download/${_tag}/${_filename}"; then + return 0 + fi + + # GitHub normalizes `~` to `.` in release asset names, while the checksum file + # still records the Debian package filename with `~dev` for correct version + # ordering. Download the normalized asset but verify it against the checksum + # entry for the original package filename. + _normalized="$(printf '%s' "$_filename" | tr '~' '.')" + if [ "$_normalized" != "$_filename" ]; then + if download "${GITHUB_URL}/releases/download/${_tag}/${_normalized}" "$_output"; then + info "using GitHub-normalized asset name ${_normalized}" + return 0 + fi fi + + return 1 } -# --------------------------------------------------------------------------- -# Platform detection -# --------------------------------------------------------------------------- +as_root() { + if [ "$(id -u)" -eq 0 ]; then + "$@" + elif has_cmd sudo; then + sudo "$@" + else + error "this installer needs root privileges; rerun as root or install sudo" + fi +} + +target_user() { + if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then + echo "$SUDO_USER" + else + id -un + fi +} + +user_home() { + _user="$1" + if has_cmd getent; then + _home="$(getent passwd "$_user" | awk -F: '{ print $6 }')" + if [ -n "$_home" ]; then + echo "$_home" + return 0 + fi + fi + + if [ "$(uname -s)" = "Darwin" ] && has_cmd dscl; then + _home="$(dscl . -read "/Users/${_user}" NFSHomeDirectory 2>/dev/null | awk '{ print $2 }')" + if [ -n "$_home" ]; then + echo "$_home" + return 0 + fi + fi + + if [ "$(id -un)" = "$_user" ]; then + echo "${HOME:-}" + return 0 + fi -get_os() { + if [ "$(uname -s)" = "Darwin" ]; then + echo "/Users/${_user}" + return 0 + fi + + echo "/home/${_user}" +} + +as_target_user() { + if [ "${PLATFORM:-}" = "darwin" ]; then + if [ "$(id -u)" -eq "$TARGET_UID" ]; then + env HOME="$TARGET_HOME" "$@" + elif has_cmd sudo; then + sudo -u "$TARGET_USER" env HOME="$TARGET_HOME" "$@" + else + error "cannot run commands as ${TARGET_USER}; install sudo or run as ${TARGET_USER}" + fi + return + fi + + _bus="unix:path=${TARGET_RUNTIME_DIR}/bus" + if [ "$(id -u)" -eq "$TARGET_UID" ]; then + env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" + elif has_cmd sudo; then + sudo -u "$TARGET_USER" env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" + elif has_cmd runuser; then + runuser -u "$TARGET_USER" -- env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" + else + error "cannot run user service commands as ${TARGET_USER}; install sudo or run as ${TARGET_USER}" + fi +} + +detect_platform() { case "$(uname -s)" in - Darwin) echo "apple-darwin" ;; - Linux) echo "unknown-linux-musl" ;; - *) error "unsupported OS: $(uname -s)" ;; + Linux) + echo "linux" + ;; + Darwin) + echo "darwin" + ;; + *) + error "unsupported OS: $(uname -s); this installer supports Linux and macOS" + ;; esac } -get_arch() { - case "$(uname -m)" in - x86_64|amd64) echo "x86_64" ;; - aarch64|arm64) echo "aarch64" ;; - *) error "unsupported architecture: $(uname -m)" ;; - esac +linux_package_method() { + if has_cmd dpkg; then + echo "deb" + elif has_cmd rpm; then + echo "rpm" + else + error "Linux installs require either dpkg or rpm" + fi +} + +set_linux_target_runtime_dir() { + if [ "$(id -u)" -eq "$TARGET_UID" ] && [ -n "${XDG_RUNTIME_DIR:-}" ]; then + TARGET_RUNTIME_DIR="$XDG_RUNTIME_DIR" + else + TARGET_RUNTIME_DIR="/run/user/${TARGET_UID}" + fi } -get_target() { - _arch="$(get_arch)" - _os="$(get_os)" - _target="${_arch}-${_os}" +check_linux_deb_platform() { + require_cmd dpkg +} + +check_macos_platform() { + _arch="$(uname -m)" - # Only these targets have published binaries. - case "$_target" in - x86_64-unknown-linux-musl|aarch64-unknown-linux-musl|aarch64-apple-darwin) ;; - x86_64-apple-darwin) error "macOS x86_64 is not supported; use Apple Silicon (aarch64) or Rosetta 2" ;; - *) error "no prebuilt binary for $_target" ;; + case "$_arch" in + arm64|aarch64) + ;; + x86_64|amd64) + error "Intel macOS is not supported because no x86_64-apple-darwin release assets are published" + ;; + *) + error "no macOS release build is published for architecture: ${_arch}" + ;; esac - echo "$_target" + if ! as_target_user brew --version >/dev/null 2>&1; then + error "Homebrew is required for macOS installs; install it from https://brew.sh" + fi } -# --------------------------------------------------------------------------- -# Version resolution -# --------------------------------------------------------------------------- +get_deb_arch() { + _arch="$(dpkg --print-architecture)" -resolve_version() { - if [ -n "${OPENSHELL_VERSION:-}" ]; then - echo "$OPENSHELL_VERSION" - return 0 + case "$_arch" in + amd64|arm64) + echo "$_arch" + ;; + *) + error "no Debian package is published for architecture: ${_arch}" + ;; + esac +} + +get_rpm_arch() { + if has_cmd rpm; then + _arch="$(rpm --eval '%{_arch}' 2>/dev/null || true)" + else + _arch="" fi - # Resolve "latest" by following the GitHub releases/latest redirect. - # GitHub redirects /releases/latest -> /releases/tag/ - info "resolving latest version..." - _latest_url="${GITHUB_URL}/releases/latest" - _resolved="$(resolve_redirect "$_latest_url")" || error "failed to resolve latest release from ${_latest_url}" + if [ -z "$_arch" ]; then + _arch="$(uname -m)" + fi - # Validate that the redirect stayed on the expected GitHub origin. - # A MITM or DNS hijack could redirect to an attacker-controlled domain, - # which would also serve a matching checksums file (making checksum - # verification useless). See: https://github.com/NVIDIA/OpenShell/issues/638 - case "$_resolved" in - https://github.com/${REPO}/releases/*) + case "$_arch" in + x86_64|amd64) + echo "x86_64" + ;; + aarch64|arm64) + echo "aarch64" ;; *) - error "unexpected redirect target: ${_resolved} (expected https://github.com/${REPO}/releases/...)" + error "no RPM package is published for architecture: ${_arch}" ;; esac +} - # Extract the tag from the resolved URL: .../releases/tag/v0.0.4 -> v0.0.4 - _version="${_resolved##*/}" +find_deb_asset() { + _checksums="$1" + _arch="$2" + + awk -v arch="$_arch" ' + $2 ~ "^\\*?openshell[-_].*[-_]" arch "\\.deb$" { + sub("^\\*", "", $2) + print $2 + exit + } + ' "$_checksums" +} - if [ -z "$_version" ] || [ "$_version" = "latest" ]; then - error "could not determine latest release version (resolved URL: ${_resolved})" +find_rpm_asset() { + _checksums="$1" + _arch="$2" + _package="$3" + + case "$_package" in + openshell) + _dev_name="openshell-dev-${_arch}.rpm" + _fallback_re="^openshell-[0-9].*\\.${_arch}\\.rpm$" + ;; + openshell-gateway) + _dev_name="openshell-gateway-dev-${_arch}.rpm" + _fallback_re="^openshell-gateway-[0-9].*\\.${_arch}\\.rpm$" + ;; + *) + error "unknown RPM package selector: ${_package}" + ;; + esac + + awk -v dev_name="$_dev_name" -v fallback_re="$_fallback_re" ' + { + name = $2 + sub("^\\*", "", name) + + if (name == dev_name) { + selected = name + found = 1 + exit + } + + if (fallback == "" && name ~ fallback_re) { + fallback = name + } + } + END { + if (found) { + print selected + } else if (fallback != "") { + print fallback + } + } + ' "$_checksums" +} + +verify_checksum() { + _archive="$1" + _checksums="$2" + _filename="$3" + + if has_cmd sha256sum; then + _expected="$(awk -v name="$_filename" '($2 == name || $2 == "*" name) { print $1; exit }' "$_checksums")" + [ -n "$_expected" ] || error "no checksum entry found for ${_filename}" + echo "$_expected $_archive" | sha256sum -c --quiet + elif has_cmd shasum; then + _expected="$(awk -v name="$_filename" '($2 == name || $2 == "*" name) { print $1; exit }' "$_checksums")" + [ -n "$_expected" ] || error "no checksum entry found for ${_filename}" + echo "$_expected $_archive" | shasum -a 256 -c --quiet + else + error "neither 'sha256sum' nor 'shasum' found; cannot verify download integrity" fi +} - echo "$_version" +install_deb_package() { + _deb_path="$1" + + if has_cmd apt-get; then + as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y \ + -o Dpkg::Options::=--force-confdef \ + -o Dpkg::Options::=--force-confnew \ + "$_deb_path" + elif has_cmd apt; then + as_root env DEBIAN_FRONTEND=noninteractive apt install -y \ + -o Dpkg::Options::=--force-confdef \ + -o Dpkg::Options::=--force-confnew \ + "$_deb_path" + else + as_root dpkg --force-confdef --force-confnew -i "$_deb_path" + fi } -# --------------------------------------------------------------------------- -# Checksum verification -# --------------------------------------------------------------------------- +install_rpm_packages() { + if has_cmd dnf; then + as_root dnf install -y "$@" + elif has_cmd yum; then + as_root yum install -y "$@" + elif has_cmd zypper; then + as_root zypper --non-interactive install --allow-unsigned-rpm "$@" + elif has_cmd rpm; then + warn "installing with rpm directly; dependencies must already be installed" + as_root rpm -Uvh --replacepkgs "$@" + else + error "'dnf', 'yum', 'zypper', or 'rpm' is required to install RPM packages" + fi +} -verify_checksum() { - _vc_archive="$1" - _vc_checksums="$2" - _vc_filename="$3" +homebrew_formula_path() { + _tap="$1" + _formula="$2" - if ! has_cmd shasum && ! has_cmd sha256sum; then - error "neither 'shasum' nor 'sha256sum' found; cannot verify download integrity" + if ! as_target_user brew tap-info "$_tap" >/dev/null 2>&1; then + info "creating local Homebrew tap ${_tap}..." + as_target_user brew tap-new --no-git "$_tap" >/dev/null fi - _vc_expected="$(grep -F "$_vc_filename" "$_vc_checksums" | awk '{print $1}')" + _tap_dir="$(as_target_user brew --repository "$_tap" 2>/dev/null || true)" + [ -n "$_tap_dir" ] || error "could not locate Homebrew tap ${_tap}" - if [ -z "$_vc_expected" ]; then - error "no checksum entry found for $_vc_filename in checksums file" + _formula_dir="${_tap_dir}/Formula" + as_target_user mkdir -p "$_formula_dir" + printf '%s/%s.rb\n' "$_formula_dir" "$_formula" +} + +patch_homebrew_formula() { + _formula_file="$1" + _patched_file="${_formula_file}.patched" + + if grep -q 'entitlements.write <<~XML' "$_formula_file"; then + info "patching Homebrew formula for idempotent postinstall..." + sed 's/entitlements\.write <<~XML/entitlements.atomic_write <<~XML/' "$_formula_file" >"$_patched_file" + mv "$_patched_file" "$_formula_file" fi - if has_cmd shasum; then - echo "$_vc_expected $_vc_archive" | shasum -a 256 -c --quiet 2>/dev/null - elif has_cmd sha256sum; then - echo "$_vc_expected $_vc_archive" | sha256sum -c --quiet 2>/dev/null + if ! grep -q 'OPENSHELL_DRIVERS:' "$_formula_file"; then + info "patching Homebrew formula to use VM driver..." + awk ' + { + print + if ($0 ~ /^[[:space:]]*environment_variables\(/) { + print " OPENSHELL_DRIVERS: \"vm\"," + } + } + ' "$_formula_file" >"$_patched_file" + mv "$_patched_file" "$_formula_file" fi } -# --------------------------------------------------------------------------- -# Install location -# --------------------------------------------------------------------------- +start_user_gateway() { + info "restarting openshell-gateway user service as ${TARGET_USER}..." -get_install_dir() { - if [ -n "${OPENSHELL_INSTALL_DIR:-}" ]; then - echo "$OPENSHELL_INSTALL_DIR" - else - echo "${HOME}/.local/bin" + if ! as_target_user systemctl --user daemon-reload; then + info "could not reach the user systemd manager for ${TARGET_USER}" + info "restart the gateway later with: systemctl --user enable openshell-gateway && systemctl --user restart openshell-gateway" + info "then register it with: openshell gateway add http://127.0.0.1:17670 --local --name local" + return 0 fi -} -# Check if a directory is already on PATH. -is_on_path() { - _dir="$1" - case ":${PATH}:" in - *":${_dir}:"*) return 0 ;; - *) return 1 ;; - esac + as_target_user systemctl --user enable openshell-gateway + as_target_user systemctl --user restart openshell-gateway + as_target_user systemctl --user is-active --quiet openshell-gateway + + wait_for_local_gateway_listener + info "registering local gateway as ${TARGET_USER}..." + register_local_gateway + wait_for_local_gateway_status } -# --------------------------------------------------------------------------- -# Main -# --------------------------------------------------------------------------- +wait_for_local_gateway_listener() { + _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" + _elapsed=0 + _last_output="" + _probe_url="http://127.0.0.1:${LOCAL_GATEWAY_PORT}/" + + info "waiting for local gateway listener to become reachable..." + while [ "$_elapsed" -lt "$_timeout" ]; do + if _last_output="$(as_target_user curl -sS --max-time 2 -o /dev/null "$_probe_url" 2>&1)"; then + info "local gateway listener is reachable" + return 0 + fi + sleep 1 + _elapsed=$((_elapsed + 1)) + done -main() { - # Parse CLI flags - for arg in "$@"; do - case "$arg" in - --help) - usage - exit 0 - ;; - *) - error "unknown option: $arg" - ;; - esac + printf '%s\n' "$_last_output" >&2 + error "local gateway listener did not become reachable at ${_probe_url} within ${_timeout}s" +} + +wait_for_local_gateway_status() { + _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" + _elapsed=0 + _status_output="" + _register_bin="${OPENSHELL_REGISTER_BIN:-openshell}" + + info "waiting for openshell status to report connected..." + while [ "$_elapsed" -lt "$_timeout" ]; do + if _status_output="$(as_target_user env NO_COLOR=1 "$_register_bin" status 2>&1)"; then + case "$_status_output" in + *"Version:"*) + info "openshell status reports connected" + return 0 + ;; + esac + fi + sleep 1 + _elapsed=$((_elapsed + 1)) done - check_downloader + printf '%s\n' "$_status_output" >&2 + error "openshell status did not report connected within ${_timeout}s" +} + +remove_local_gateway_registration() { + [ -n "$TARGET_HOME" ] || error "cannot resolve home directory for ${TARGET_USER}" + _config_dir="${TARGET_HOME}/.config/openshell" + + # The package-installed gateway is a user service. Replace the CLI registration + # directly instead of asking `gateway destroy` to tear down Docker resources. + # shellcheck disable=SC2016 + as_target_user sh -c ' + config_dir=$1 + rm -rf "${config_dir}/gateways/local" + active="${config_dir}/active_gateway" + if [ "$(cat "$active" 2>/dev/null || true)" = "local" ]; then + rm -f "$active" + fi + ' sh "$_config_dir" +} - _version="$(resolve_version)" - _target="$(get_target)" - _filename="${APP_NAME}-${_target}.tar.gz" - _download_url="${GITHUB_URL}/releases/download/${_version}/${_filename}" - _checksums_url="${GITHUB_URL}/releases/download/${_version}/${APP_NAME}-checksums-sha256.txt" - _install_dir="$(get_install_dir)" +register_local_gateway() { + _register_bin="${OPENSHELL_REGISTER_BIN:-openshell}" + + if _add_output="$(as_target_user "$_register_bin" gateway add "http://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name local 2>&1)"; then + [ -z "$_add_output" ] || print_gateway_add_output "$_add_output" + return 0 + else + _add_status=$? + fi - info "downloading ${APP_NAME} ${_version} (${_target})..." + case "$_add_output" in + *"already exists"*) + info "local gateway already exists; removing and re-adding it..." + remove_local_gateway_registration + as_target_user "$_register_bin" gateway add "http://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name local + ;; + *) + printf '%s\n' "$_add_output" >&2 + return "$_add_status" + ;; + esac +} +print_gateway_add_output() { + printf '%s\n' "$1" | while IFS= read -r _line; do + case "$_line" in + *"Gateway is not reachable at http://127.0.0.1:${LOCAL_GATEWAY_PORT}"*) ;; + *"Verify the gateway is running and the endpoint is correct."*) ;; + *) printf '%s\n' "$_line" >&2 ;; + esac + done +} + +install_linux_deb() { + check_linux_deb_platform + set_linux_target_runtime_dir + + _arch="$(get_deb_arch)" _tmpdir="$(mktemp -d)" + chmod 0755 "$_tmpdir" trap 'rm -rf "$_tmpdir"' EXIT - if ! download "$_download_url" "${_tmpdir}/${_filename}"; then - error "failed to download ${_download_url}" + _checksums_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${CHECKSUMS_NAME}" + info "downloading ${RELEASE_TAG} release checksums..." + download "$_checksums_url" "${_tmpdir}/${CHECKSUMS_NAME}" || { + error "failed to download ${_checksums_url}" + } + + _deb_file="$(find_deb_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch")" + if [ -z "$_deb_file" ]; then + error "no Debian package found for architecture: ${_arch}" fi - # Verify checksum (mandatory — never skip) + _deb_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${_deb_file}" + _deb_path="${_tmpdir}/${_deb_file}" + + info "selected ${_deb_file}" + + info "downloading ${_deb_file}..." + download_release_asset "$RELEASE_TAG" "$_deb_file" "$_deb_path" || { + error "failed to download ${_deb_url}" + } + chmod 0644 "$_deb_path" + info "verifying checksum..." - if ! download "$_checksums_url" "${_tmpdir}/checksums.txt"; then - error "failed to download checksums file from ${_checksums_url}" + verify_checksum "$_deb_path" "${_tmpdir}/${CHECKSUMS_NAME}" "$_deb_file" + + info "installing ${_deb_file}..." + install_deb_package "$_deb_path" + info "installed ${APP_NAME} package from ${RELEASE_TAG}" + start_user_gateway +} + +install_linux_rpm() { + require_cmd rpm + set_linux_target_runtime_dir + + _arch="$(get_rpm_arch)" + _tmpdir="$(mktemp -d)" + chmod 0755 "$_tmpdir" + trap 'rm -rf "$_tmpdir"' EXIT + + _checksums_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${CHECKSUMS_NAME}" + info "downloading ${RELEASE_TAG} release checksums..." + download "$_checksums_url" "${_tmpdir}/${CHECKSUMS_NAME}" || { + error "failed to download ${_checksums_url}" + } + + _rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell)" + if [ -z "$_rpm_file" ]; then + error "no openshell RPM package found for architecture: ${_arch}" fi - if ! verify_checksum "${_tmpdir}/${_filename}" "${_tmpdir}/checksums.txt" "$_filename"; then - error "checksum verification failed for ${_filename}" + + _gateway_rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell-gateway)" + if [ -z "$_gateway_rpm_file" ]; then + error "no openshell-gateway RPM package found for architecture: ${_arch}" fi - # Extract - info "extracting..." - tar -xzf "${_tmpdir}/${_filename}" -C "${_tmpdir}" --no-same-owner --no-same-permissions "${APP_NAME}" + info "selected ${_rpm_file} and ${_gateway_rpm_file}" + + for _package_file in "$_rpm_file" "$_gateway_rpm_file"; do + _package_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${_package_file}" + _package_path="${_tmpdir}/${_package_file}" + + info "downloading ${_package_file}..." + download_release_asset "$RELEASE_TAG" "$_package_file" "$_package_path" || { + error "failed to download ${_package_url}" + } + chmod 0644 "$_package_path" + + info "verifying checksum for ${_package_file}..." + verify_checksum "$_package_path" "${_tmpdir}/${CHECKSUMS_NAME}" "$_package_file" + done + + info "installing ${_rpm_file} and ${_gateway_rpm_file}..." + install_rpm_packages "${_tmpdir}/${_rpm_file}" "${_tmpdir}/${_gateway_rpm_file}" + info "installed ${APP_NAME} RPM packages from ${RELEASE_TAG}" + start_user_gateway +} + +install_macos_homebrew() { + check_macos_platform - # Install - mkdir -p "$_install_dir" 2>/dev/null || true + _tmpdir="$(mktemp -d)" + chmod 0755 "$_tmpdir" + trap 'rm -rf "$_tmpdir"' EXIT + + _formula_file="${_tmpdir}/openshell.rb" + _formula_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/openshell.rb" + + info "downloading Homebrew formula from ${_formula_url}..." + download_release_asset "$RELEASE_TAG" "openshell.rb" "$_formula_file" || { + error "failed to download ${_formula_url}; the selected release may not include a Homebrew formula" + } + chmod 0644 "$_formula_file" + patch_homebrew_formula "$_formula_file" + + _tap_formula_file="$(homebrew_formula_path "$HOMEBREW_TAP" "$HOMEBREW_FORMULA_NAME")" + info "staging Homebrew formula in tap ${HOMEBREW_TAP}..." + cp "$_formula_file" "$_tap_formula_file" + chmod 0644 "$_tap_formula_file" + if [ "$(id -u)" -eq 0 ]; then + chown "$TARGET_USER" "$_tap_formula_file" 2>/dev/null || true + fi - if [ -w "$_install_dir" ] || mkdir -p "$_install_dir" 2>/dev/null; then - install -m 755 "${_tmpdir}/${APP_NAME}" "${_install_dir}/${APP_NAME}" + _formula_ref="${HOMEBREW_TAP}/${HOMEBREW_FORMULA_NAME}" + + if as_target_user brew list --formula openshell >/dev/null 2>&1; then + info "reinstalling OpenShell with Homebrew..." + as_target_user brew reinstall --formula "$_formula_ref" else - info "elevated permissions required to install to ${_install_dir}" - sudo mkdir -p "$_install_dir" - sudo install -m 755 "${_tmpdir}/${APP_NAME}" "${_install_dir}/${APP_NAME}" - fi - - _installed_version="$("${_install_dir}/${APP_NAME}" --version 2>/dev/null || echo "${_version}")" - info "installed ${_installed_version} to ${_install_dir}/${APP_NAME}" - - # If the install directory isn't on PATH, print instructions - if ! is_on_path "$_install_dir"; then - echo "" - info "${_install_dir} is not on your PATH." - info "" - info "Add it by appending the following to your shell configuration file" - info "(e.g. ~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish):" - info "" - - _current_shell="$(basename "${SHELL:-sh}" 2>/dev/null || echo "sh")" - case "$_current_shell" in - fish) - info " fish_add_path ${_install_dir}" + info "installing OpenShell with Homebrew..." + as_target_user brew install --formula "$_formula_ref" + fi + + info "restarting OpenShell Homebrew service..." + if ! as_target_user brew services restart "$_formula_ref"; then + warn "could not restart the OpenShell Homebrew service" + info "restart it later with: brew services restart ${_formula_ref}" + info "then register it with: openshell gateway add http://127.0.0.1:${LOCAL_GATEWAY_PORT} --local --name local" + return 0 + fi + + _brew_prefix="$(as_target_user brew --prefix 2>/dev/null || true)" + if [ -n "$_brew_prefix" ] && [ -x "${_brew_prefix}/bin/openshell" ]; then + OPENSHELL_REGISTER_BIN="${_brew_prefix}/bin/openshell" + fi + + wait_for_local_gateway_listener + info "registering local gateway as ${TARGET_USER}..." + register_local_gateway + wait_for_local_gateway_status +} + +main() { + if [ "$#" -gt 0 ]; then + case "$1" in + --help) + usage + exit 0 ;; *) - info " export PATH=\"${_install_dir}:\$PATH\"" + error "unknown option: $1" ;; esac - - info "" - info "Then restart your shell or run the command above in your current session." fi + + require_cmd curl + RELEASE_TAG="$(resolve_release_tag)" + PLATFORM="$(detect_platform)" + + TARGET_USER="$(target_user)" + TARGET_UID="$(id -u "$TARGET_USER" 2>/dev/null || true)" + [ -n "$TARGET_UID" ] || error "cannot resolve uid for ${TARGET_USER}" + TARGET_HOME="$(user_home "$TARGET_USER")" + + case "$PLATFORM" in + linux) + case "$(linux_package_method)" in + deb) + install_linux_deb + ;; + rpm) + install_linux_rpm + ;; + *) + error "unsupported Linux package method" + ;; + esac + ;; + darwin) + install_macos_homebrew + ;; + *) + error "unsupported platform: ${PLATFORM}" + ;; + esac } main "$@" From f689f7e0de44c2f88ec8c661e3aec1bf1361b9c4 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:26:05 -0700 Subject: [PATCH 02/10] ci(installer): remove legacy install workflow --- .github/workflows/test-install.yml | 51 ------------------------------ 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/test-install.yml diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml deleted file mode 100644 index 9a5b43767..000000000 --- a/.github/workflows/test-install.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Test Legacy Install Script - -on: - pull_request: - paths: - - 'install-legacy.sh' - - 'e2e/install/**' - - '.github/workflows/test-install.yml' - push: - branches: [main] - paths: - - 'install-legacy.sh' - - 'e2e/install/**' - - '.github/workflows/test-install.yml' - workflow_dispatch: - -permissions: - contents: read - -jobs: - test-install: - name: install-legacy.sh (${{ matrix.shell }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - shell: sh - test: e2e/install/sh_test.sh - run: sh e2e/install/sh_test.sh - - shell: bash - test: e2e/install/bash_test.sh - run: bash e2e/install/bash_test.sh - - shell: zsh - test: e2e/install/zsh_test.sh - run: zsh e2e/install/zsh_test.sh - install: zsh - - shell: fish - test: e2e/install/fish_test.fish - run: fish e2e/install/fish_test.fish - install: fish - - steps: - - uses: actions/checkout@v6 - - - name: Install ${{ matrix.shell }} - if: matrix.install - run: sudo apt-get update && sudo apt-get install -y ${{ matrix.install }} - - - name: Run tests (${{ matrix.shell }}) - run: ${{ matrix.run }} From cc4d9f6309d443e88d1a5a7537f8b1af6ac4593f Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:28:50 -0700 Subject: [PATCH 03/10] test(installer): remove legacy install e2e scripts --- e2e/install/bash_test.sh | 80 ------------------- e2e/install/fish_test.fish | 152 ------------------------------------- e2e/install/helpers.sh | 100 ------------------------ e2e/install/sh_test.sh | 105 ------------------------- e2e/install/zsh_test.sh | 80 ------------------- 5 files changed, 517 deletions(-) delete mode 100755 e2e/install/bash_test.sh delete mode 100755 e2e/install/fish_test.fish delete mode 100644 e2e/install/helpers.sh delete mode 100755 e2e/install/sh_test.sh delete mode 100755 e2e/install/zsh_test.sh diff --git a/e2e/install/bash_test.sh b/e2e/install/bash_test.sh deleted file mode 100755 index 7546f3a5f..000000000 --- a/e2e/install/bash_test.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Bash e2e tests for install-legacy.sh. -# -# Downloads the latest legacy tarball release for real and validates: -# - Binary is installed to the correct directory -# - Binary is executable and runs -# - PATH guidance shows the correct export command for bash -# -set -euo pipefail - -. "$(dirname "$0")/helpers.sh" - -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - -test_binary_installed() { - printf 'TEST: binary exists in install directory\n' - - if [ -f "$INSTALL_DIR/openshell" ]; then - pass "openshell binary exists at $INSTALL_DIR/openshell" - else - fail "openshell binary exists" "not found at $INSTALL_DIR/openshell" - fi -} - -test_binary_executable() { - printf 'TEST: binary is executable\n' - - if [ -x "$INSTALL_DIR/openshell" ]; then - pass "openshell binary is executable" - else - fail "openshell binary is executable" "$INSTALL_DIR/openshell is not executable" - fi -} - -test_binary_runs() { - printf 'TEST: binary runs successfully\n' - - if _version="$("$INSTALL_DIR/openshell" --version 2>/dev/null)"; then - pass "openshell --version succeeds: $_version" - else - fail "openshell --version succeeds" "exit code: $?" - fi -} - -test_guidance_shows_export_path() { - printf 'TEST: guidance shows export PATH for bash users\n' - - assert_output_contains "$INSTALL_OUTPUT" 'export PATH="' "shows export PATH command" - assert_output_not_contains "$INSTALL_OUTPUT" "fish_add_path" "does not show fish command" -} - -test_guidance_mentions_not_on_path() { - printf 'TEST: guidance mentions install dir is not on PATH\n' - - assert_output_contains "$INSTALL_OUTPUT" "is not on your PATH" "mentions PATH issue" - assert_output_contains "$INSTALL_OUTPUT" "$INSTALL_DIR" "includes install dir in guidance" -} - -# --------------------------------------------------------------------------- -# Runner -# --------------------------------------------------------------------------- - -printf '=== install-legacy.sh e2e tests: bash ===\n\n' - -printf 'Installing openshell...\n' -SHELL="/bin/bash" run_install -printf 'Done.\n\n' - -test_binary_installed; echo "" -test_binary_executable; echo "" -test_binary_runs; echo "" -test_guidance_shows_export_path; echo "" -test_guidance_mentions_not_on_path - -print_summary diff --git a/e2e/install/fish_test.fish b/e2e/install/fish_test.fish deleted file mode 100755 index 46c831b46..000000000 --- a/e2e/install/fish_test.fish +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env fish -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Fish e2e tests for install-legacy.sh. -# -# Downloads the latest legacy tarball release for real and validates: -# - Binary is installed to the correct directory -# - Binary is executable and runs -# - PATH guidance shows fish_add_path (not export PATH) - -set -g PASS 0 -set -g FAIL 0 - -# Resolve paths relative to this script -set -g SCRIPT_DIR (builtin cd (dirname (status filename)) && pwd) -set -g REPO_ROOT (builtin cd "$SCRIPT_DIR/../.." && pwd) -set -g INSTALL_SCRIPT "$REPO_ROOT/install-legacy.sh" - -# Set by run_install -set -g INSTALL_DIR "" -set -g INSTALL_OUTPUT "" - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -function pass - set -g PASS (math $PASS + 1) - printf ' PASS: %s\n' $argv[1] -end - -function fail - set -g FAIL (math $FAIL + 1) - printf ' FAIL: %s\n' $argv[1] >&2 - if test (count $argv) -gt 1 - printf ' %s\n' $argv[2] >&2 - end -end - -function assert_output_contains - set -l output $argv[1] - set -l pattern $argv[2] - set -l label $argv[3] - - if string match -q -- "*$pattern*" "$output" - pass "$label" - else - fail "$label" "expected '$pattern' in output" - end -end - -function assert_output_not_contains - set -l output $argv[1] - set -l pattern $argv[2] - set -l label $argv[3] - - if string match -q -- "*$pattern*" "$output" - fail "$label" "unexpected '$pattern' found in output" - else - pass "$label" - end -end - -function run_install - set -g INSTALL_DIR (mktemp -d)/bin - - set -g INSTALL_OUTPUT (OPENSHELL_INSTALL_DIR="$INSTALL_DIR" \ - SHELL="/usr/bin/fish" \ - PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \ - sh "$INSTALL_SCRIPT" 2>&1) - - if test $status -ne 0 - printf 'install-legacy.sh failed:\n%s\n' "$INSTALL_OUTPUT" >&2 - return 1 - end -end - -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - -function test_binary_installed - printf 'TEST: binary exists in install directory\n' - - if test -f "$INSTALL_DIR/openshell" - pass "openshell binary exists at $INSTALL_DIR/openshell" - else - fail "openshell binary exists" "not found at $INSTALL_DIR/openshell" - end -end - -function test_binary_executable - printf 'TEST: binary is executable\n' - - if test -x "$INSTALL_DIR/openshell" - pass "openshell binary is executable" - else - fail "openshell binary is executable" "$INSTALL_DIR/openshell is not executable" - end -end - -function test_binary_runs - printf 'TEST: binary runs successfully\n' - - set -l version_output ("$INSTALL_DIR/openshell" --version 2>/dev/null) - if test $status -eq 0 - pass "openshell --version succeeds: $version_output" - else - fail "openshell --version succeeds" "exit code: $status" - end -end - -function test_guidance_shows_fish_add_path - printf 'TEST: guidance shows fish_add_path for fish users\n' - - assert_output_contains "$INSTALL_OUTPUT" "fish_add_path" "shows fish_add_path command" - assert_output_not_contains "$INSTALL_OUTPUT" 'export PATH="' "does not show POSIX export" -end - -function test_guidance_mentions_not_on_path - printf 'TEST: guidance mentions install dir is not on PATH\n' - - assert_output_contains "$INSTALL_OUTPUT" "is not on your PATH" "mentions PATH issue" - assert_output_contains "$INSTALL_OUTPUT" "$INSTALL_DIR" "includes install dir in guidance" -end - -# --------------------------------------------------------------------------- -# Runner -# --------------------------------------------------------------------------- - -printf '=== install-legacy.sh e2e tests: fish ===\n\n' - -printf 'Installing openshell...\n' -run_install -printf 'Done.\n\n' - -test_binary_installed -echo "" -test_binary_executable -echo "" -test_binary_runs -echo "" -test_guidance_shows_fish_add_path -echo "" -test_guidance_mentions_not_on_path - -printf '\n=== Results: %d passed, %d failed ===\n' $PASS $FAIL - -if test $FAIL -gt 0 - exit 1 -end diff --git a/e2e/install/helpers.sh b/e2e/install/helpers.sh deleted file mode 100644 index afc6a2bcc..000000000 --- a/e2e/install/helpers.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Shared test helpers for install-legacy.sh e2e tests. -# Sourced by each per-shell test file (except fish, which has its own helpers). -# -# Provides: -# - pass / fail / print_summary -# - assert_output_contains / assert_output_not_contains -# - run_install (runs install-legacy.sh to a temp dir, captures output) -# - REPO_ROOT / INSTALL_SCRIPT paths -# - INSTALL_DIR / INSTALL_OUTPUT (set after run_install) - -HELPERS_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$HELPERS_DIR/../.." && pwd)" -INSTALL_SCRIPT="$REPO_ROOT/install-legacy.sh" - -_PASS=0 -_FAIL=0 - -# Set by run_install -INSTALL_DIR="" -INSTALL_OUTPUT="" - -# --------------------------------------------------------------------------- -# Assertions -# --------------------------------------------------------------------------- - -pass() { - _PASS=$((_PASS + 1)) - printf ' PASS: %s\n' "$1" -} - -fail() { - _FAIL=$((_FAIL + 1)) - printf ' FAIL: %s\n' "$1" >&2 - if [ -n "${2:-}" ]; then - printf ' %s\n' "$2" >&2 - fi -} - -assert_output_contains() { - _aoc_output="$1" - _aoc_pattern="$2" - _aoc_label="$3" - - if printf '%s' "$_aoc_output" | grep -qF "$_aoc_pattern"; then - pass "$_aoc_label" - else - fail "$_aoc_label" "expected '$_aoc_pattern' in output" - fi -} - -assert_output_not_contains() { - _aonc_output="$1" - _aonc_pattern="$2" - _aonc_label="$3" - - if printf '%s' "$_aonc_output" | grep -qF "$_aonc_pattern"; then - fail "$_aonc_label" "unexpected '$_aonc_pattern' found in output" - else - pass "$_aonc_label" - fi -} - -# --------------------------------------------------------------------------- -# Install runner -# --------------------------------------------------------------------------- - -# Run install-legacy.sh, installing to a temp directory with the install -# dir removed from PATH so we always get PATH guidance output. -# -# Sets INSTALL_DIR and INSTALL_OUTPUT for subsequent assertions. -# The SHELL variable is passed through so tests can control which shell -# guidance is shown. -# -# Usage: -# SHELL="/bin/bash" run_install -run_install() { - INSTALL_DIR="$(mktemp -d)/bin" - - # Remove the install dir from PATH (it won't be there, but be explicit). - # Keep a minimal PATH so curl/tar/install are available. - INSTALL_OUTPUT="$(OPENSHELL_INSTALL_DIR="$INSTALL_DIR" \ - PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \ - sh "$INSTALL_SCRIPT" 2>&1)" || { - printf 'install-legacy.sh failed:\n%s\n' "$INSTALL_OUTPUT" >&2 - return 1 - } -} - -# --------------------------------------------------------------------------- -# Summary -# --------------------------------------------------------------------------- - -print_summary() { - printf '\n=== Results: %d passed, %d failed ===\n' "$_PASS" "$_FAIL" - [ "$_FAIL" -eq 0 ] -} diff --git a/e2e/install/sh_test.sh b/e2e/install/sh_test.sh deleted file mode 100755 index 38a9dc7d8..000000000 --- a/e2e/install/sh_test.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# POSIX sh e2e tests for install-legacy.sh. -# -# Downloads the latest legacy tarball release for real and validates: -# - Binary is installed to the correct directory -# - Binary is executable and runs -# - PATH guidance shows the correct export command for sh -# - No rc files or env scripts are created -# -set -eu - -. "$(dirname "$0")/helpers.sh" - -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - -test_binary_installed() { - printf 'TEST: binary exists in install directory\n' - - if [ -f "$INSTALL_DIR/openshell" ]; then - pass "openshell binary exists at $INSTALL_DIR/openshell" - else - fail "openshell binary exists" "not found at $INSTALL_DIR/openshell" - fi -} - -test_binary_executable() { - printf 'TEST: binary is executable\n' - - if [ -x "$INSTALL_DIR/openshell" ]; then - pass "openshell binary is executable" - else - fail "openshell binary is executable" "$INSTALL_DIR/openshell is not executable" - fi -} - -test_binary_runs() { - printf 'TEST: binary runs successfully\n' - - if _version="$("$INSTALL_DIR/openshell" --version 2>/dev/null)"; then - pass "openshell --version succeeds: $_version" - else - fail "openshell --version succeeds" "exit code: $?" - fi -} - -test_guidance_shows_export_path() { - printf 'TEST: guidance shows export PATH for sh users\n' - - assert_output_contains "$INSTALL_OUTPUT" 'export PATH="' "shows export PATH command" - assert_output_not_contains "$INSTALL_OUTPUT" "fish_add_path" "does not show fish command" -} - -test_guidance_mentions_not_on_path() { - printf 'TEST: guidance mentions install dir is not on PATH\n' - - assert_output_contains "$INSTALL_OUTPUT" "is not on your PATH" "mentions PATH issue" - assert_output_contains "$INSTALL_OUTPUT" "$INSTALL_DIR" "includes install dir in guidance" -} - -test_guidance_mentions_restart() { - printf 'TEST: guidance tells user to restart shell\n' - - assert_output_contains "$INSTALL_OUTPUT" "restart your shell" "mentions shell restart" -} - -test_no_env_scripts_created() { - printf 'TEST: no env scripts are created in install dir\n' - - if [ -f "$INSTALL_DIR/env" ]; then - fail "no env script created" "found $INSTALL_DIR/env" - else - pass "no env script created" - fi - - if [ -f "$INSTALL_DIR/env.fish" ]; then - fail "no env.fish script created" "found $INSTALL_DIR/env.fish" - else - pass "no env.fish script created" - fi -} - -# --------------------------------------------------------------------------- -# Runner -# --------------------------------------------------------------------------- - -printf '=== install-legacy.sh e2e tests: sh ===\n\n' - -printf 'Installing openshell...\n' -SHELL="/bin/sh" run_install -printf 'Done.\n\n' - -test_binary_installed; echo "" -test_binary_executable; echo "" -test_binary_runs; echo "" -test_guidance_shows_export_path; echo "" -test_guidance_mentions_not_on_path; echo "" -test_guidance_mentions_restart; echo "" -test_no_env_scripts_created - -print_summary diff --git a/e2e/install/zsh_test.sh b/e2e/install/zsh_test.sh deleted file mode 100755 index 1531bba15..000000000 --- a/e2e/install/zsh_test.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/zsh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Zsh e2e tests for install-legacy.sh. -# -# Downloads the latest legacy tarball release for real and validates: -# - Binary is installed to the correct directory -# - Binary is executable and runs -# - PATH guidance shows the correct export command for zsh -# -set -eu - -. "$(dirname "$0")/helpers.sh" - -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - -test_binary_installed() { - printf 'TEST: binary exists in install directory\n' - - if [ -f "$INSTALL_DIR/openshell" ]; then - pass "openshell binary exists at $INSTALL_DIR/openshell" - else - fail "openshell binary exists" "not found at $INSTALL_DIR/openshell" - fi -} - -test_binary_executable() { - printf 'TEST: binary is executable\n' - - if [ -x "$INSTALL_DIR/openshell" ]; then - pass "openshell binary is executable" - else - fail "openshell binary is executable" "$INSTALL_DIR/openshell is not executable" - fi -} - -test_binary_runs() { - printf 'TEST: binary runs successfully\n' - - if _version="$("$INSTALL_DIR/openshell" --version 2>/dev/null)"; then - pass "openshell --version succeeds: $_version" - else - fail "openshell --version succeeds" "exit code: $?" - fi -} - -test_guidance_shows_export_path() { - printf 'TEST: guidance shows export PATH for zsh users\n' - - assert_output_contains "$INSTALL_OUTPUT" 'export PATH="' "shows export PATH command" - assert_output_not_contains "$INSTALL_OUTPUT" "fish_add_path" "does not show fish command" -} - -test_guidance_mentions_not_on_path() { - printf 'TEST: guidance mentions install dir is not on PATH\n' - - assert_output_contains "$INSTALL_OUTPUT" "is not on your PATH" "mentions PATH issue" - assert_output_contains "$INSTALL_OUTPUT" "$INSTALL_DIR" "includes install dir in guidance" -} - -# --------------------------------------------------------------------------- -# Runner -# --------------------------------------------------------------------------- - -printf '=== install-legacy.sh e2e tests: zsh ===\n\n' - -printf 'Installing openshell...\n' -SHELL="/bin/zsh" run_install -printf 'Done.\n\n' - -test_binary_installed; echo "" -test_binary_executable; echo "" -test_binary_runs; echo "" -test_guidance_shows_export_path; echo "" -test_guidance_mentions_not_on_path - -print_summary From bf146f77aa9578bdb711ced020a8245357e6e6f6 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 15:51:18 -0700 Subject: [PATCH 04/10] fix(installer): register gateway before listener probe --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 2cc2525d6..a4291c407 100755 --- a/install.sh +++ b/install.sh @@ -457,9 +457,9 @@ start_user_gateway() { as_target_user systemctl --user restart openshell-gateway as_target_user systemctl --user is-active --quiet openshell-gateway - wait_for_local_gateway_listener info "registering local gateway as ${TARGET_USER}..." register_local_gateway + wait_for_local_gateway_listener wait_for_local_gateway_status } @@ -692,9 +692,9 @@ install_macos_homebrew() { OPENSHELL_REGISTER_BIN="${_brew_prefix}/bin/openshell" fi - wait_for_local_gateway_listener info "registering local gateway as ${TARGET_USER}..." register_local_gateway + wait_for_local_gateway_listener wait_for_local_gateway_status } From 85a0faf9f04f38de8500a7561a4a8d3d1f66df1f Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 15:59:14 -0700 Subject: [PATCH 05/10] fix(installer): use mtls for package gateway --- install.sh | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/install.sh b/install.sh index a4291c407..1cc25b9cc 100755 --- a/install.sh +++ b/install.sh @@ -449,7 +449,7 @@ start_user_gateway() { if ! as_target_user systemctl --user daemon-reload; then info "could not reach the user systemd manager for ${TARGET_USER}" info "restart the gateway later with: systemctl --user enable openshell-gateway && systemctl --user restart openshell-gateway" - info "then register it with: openshell gateway add http://127.0.0.1:17670 --local --name local" + info "then register it with: openshell gateway add https://127.0.0.1:17670 --local --name openshell" return 0 fi @@ -467,11 +467,14 @@ wait_for_local_gateway_listener() { _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" _elapsed=0 _last_output="" - _probe_url="http://127.0.0.1:${LOCAL_GATEWAY_PORT}/" + _probe_url="https://127.0.0.1:${LOCAL_GATEWAY_PORT}/" + _mtls_dir="${TARGET_HOME}/.config/openshell/gateways/openshell/mtls" info "waiting for local gateway listener to become reachable..." while [ "$_elapsed" -lt "$_timeout" ]; do - if _last_output="$(as_target_user curl -sS --max-time 2 -o /dev/null "$_probe_url" 2>&1)"; then + if [ ! -f "${_mtls_dir}/ca.crt" ] || [ ! -f "${_mtls_dir}/tls.crt" ] || [ ! -f "${_mtls_dir}/tls.key" ]; then + _last_output="mTLS client bundle is not ready under ${_mtls_dir}" + elif _last_output="$(as_target_user curl -sS --max-time 2 --cacert "${_mtls_dir}/ca.crt" --cert "${_mtls_dir}/tls.crt" --key "${_mtls_dir}/tls.key" -o /dev/null "$_probe_url" 2>&1)"; then info "local gateway listener is reachable" return 0 fi @@ -517,8 +520,15 @@ remove_local_gateway_registration() { as_target_user sh -c ' config_dir=$1 rm -rf "${config_dir}/gateways/local" + mkdir -p "${config_dir}/gateways/openshell" + rm -f \ + "${config_dir}/gateways/openshell/metadata.json" \ + "${config_dir}/gateways/openshell/edge_token" \ + "${config_dir}/gateways/openshell/cf_token" \ + "${config_dir}/gateways/openshell/oidc_token.json" active="${config_dir}/active_gateway" - if [ "$(cat "$active" 2>/dev/null || true)" = "local" ]; then + active_name="$(cat "$active" 2>/dev/null || true)" + if [ "$active_name" = "local" ] || [ "$active_name" = "openshell" ]; then rm -f "$active" fi ' sh "$_config_dir" @@ -527,7 +537,7 @@ remove_local_gateway_registration() { register_local_gateway() { _register_bin="${OPENSHELL_REGISTER_BIN:-openshell}" - if _add_output="$(as_target_user "$_register_bin" gateway add "http://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name local 2>&1)"; then + if _add_output="$(as_target_user "$_register_bin" gateway add "https://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name openshell 2>&1)"; then [ -z "$_add_output" ] || print_gateway_add_output "$_add_output" return 0 else @@ -538,7 +548,7 @@ register_local_gateway() { *"already exists"*) info "local gateway already exists; removing and re-adding it..." remove_local_gateway_registration - as_target_user "$_register_bin" gateway add "http://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name local + as_target_user "$_register_bin" gateway add "https://127.0.0.1:${LOCAL_GATEWAY_PORT}" --local --name openshell ;; *) printf '%s\n' "$_add_output" >&2 @@ -550,7 +560,7 @@ register_local_gateway() { print_gateway_add_output() { printf '%s\n' "$1" | while IFS= read -r _line; do case "$_line" in - *"Gateway is not reachable at http://127.0.0.1:${LOCAL_GATEWAY_PORT}"*) ;; + *"Gateway is not reachable at https://127.0.0.1:${LOCAL_GATEWAY_PORT}"*) ;; *"Verify the gateway is running and the endpoint is correct."*) ;; *) printf '%s\n' "$_line" >&2 ;; esac @@ -683,7 +693,7 @@ install_macos_homebrew() { if ! as_target_user brew services restart "$_formula_ref"; then warn "could not restart the OpenShell Homebrew service" info "restart it later with: brew services restart ${_formula_ref}" - info "then register it with: openshell gateway add http://127.0.0.1:${LOCAL_GATEWAY_PORT} --local --name local" + info "then register it with: openshell gateway add https://127.0.0.1:${LOCAL_GATEWAY_PORT} --local --name openshell" return 0 fi From 46a31d05675fff6826c42911ae1cc884dcd304e3 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 16:05:01 -0700 Subject: [PATCH 06/10] fix(installer): sync promoted script with dev installer --- install.sh | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/install.sh b/install.sh index 1cc25b9cc..98d2d5d77 100755 --- a/install.sh +++ b/install.sh @@ -114,10 +114,10 @@ download_release_asset() { return 0 fi - # GitHub normalizes `~` to `.` in release asset names, while the checksum file - # still records the Debian package filename with `~dev` for correct version - # ordering. Download the normalized asset but verify it against the checksum - # entry for the original package filename. + # GitHub normalizes `~` to `.` in release asset names, while checksum files + # can still record package filenames with `~dev` for correct version ordering. + # Download the normalized asset but verify it against the checksum entry for + # the original package filename. _normalized="$(printf '%s' "$_filename" | tr '~' '.')" if [ "$_normalized" != "$_filename" ]; then if download "${GITHUB_URL}/releases/download/${_tag}/${_normalized}" "$_output"; then @@ -429,18 +429,6 @@ patch_homebrew_formula() { mv "$_patched_file" "$_formula_file" fi - if ! grep -q 'OPENSHELL_DRIVERS:' "$_formula_file"; then - info "patching Homebrew formula to use VM driver..." - awk ' - { - print - if ($0 ~ /^[[:space:]]*environment_variables\(/) { - print " OPENSHELL_DRIVERS: \"vm\"," - } - } - ' "$_formula_file" >"$_patched_file" - mv "$_patched_file" "$_formula_file" - fi } start_user_gateway() { @@ -514,7 +502,7 @@ remove_local_gateway_registration() { [ -n "$TARGET_HOME" ] || error "cannot resolve home directory for ${TARGET_USER}" _config_dir="${TARGET_HOME}/.config/openshell" - # The package-installed gateway is a user service. Replace the CLI registration + # The install-dev gateway is a user service. Replace the CLI registration # directly instead of asking `gateway destroy` to tear down Docker resources. # shellcheck disable=SC2016 as_target_user sh -c ' From 82eb3eff5b7fe91ee2147fab1f9b037ec05a0f05 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 16:22:45 -0700 Subject: [PATCH 07/10] ci(release): configure vm driver for macos canary --- .github/workflows/release-canary.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index bd1192ec4..f03aa04a3 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -20,6 +20,10 @@ jobs: runs-on: macos-latest-xlarge timeout-minutes: 20 steps: + - name: Ensure VM driver + run: | + launchctl setenv OPENSHELL_DRIVERS vm + - name: Install dev and check status run: | curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh From 010956bbc7ca70157cbb4fd9eaa1fb378cf38833 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 16:30:15 -0700 Subject: [PATCH 08/10] ci(release): stop pinning dev in canary --- .github/workflows/release-canary.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index f03aa04a3..61f8a8a1e 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -24,9 +24,9 @@ jobs: run: | launchctl setenv OPENSHELL_DRIVERS vm - - name: Install dev and check status + - name: Install and check status run: | - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status ubuntu: @@ -46,9 +46,9 @@ jobs: printf 'OPENSHELL_DRIVERS=docker\n' > "${HOME}/.config/openshell/gateway.env" docker info - - name: Install dev and check status + - name: Install and check status run: | - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status fedora: @@ -67,7 +67,7 @@ jobs: printf 'OPENSHELL_DRIVERS=podman\n' > "${HOME}/.config/openshell/gateway.env" podman info - - name: Install dev and check status + - name: Install and check status run: | - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | OPENSHELL_VERSION=dev sh + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status From d20074574eb456fe5f7a1d7c9c6a028a30d6612a Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 16:40:49 -0700 Subject: [PATCH 09/10] ci(release): smoke test sandbox creation --- .github/workflows/release-canary.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 61f8a8a1e..3e17ee247 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -29,6 +29,14 @@ jobs: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status + - name: Create sandbox + run: | + openshell sandbox create \ + --no-keep \ + --no-tty \ + --no-auto-providers \ + -- /bin/sh -lc 'echo openshell-canary-sandbox-ok' + ubuntu: name: Ubuntu Docker if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} @@ -51,6 +59,14 @@ jobs: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status + - name: Create sandbox + run: | + openshell sandbox create \ + --no-keep \ + --no-tty \ + --no-auto-providers \ + -- /bin/sh -lc 'echo openshell-canary-sandbox-ok' + fedora: name: Fedora RPM if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} @@ -71,3 +87,11 @@ jobs: run: | curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status + + - name: Create sandbox + run: | + openshell sandbox create \ + --no-keep \ + --no-tty \ + --no-auto-providers \ + -- /bin/sh -lc 'echo openshell-canary-sandbox-ok' From f267a2252ad9f22bf5979557630dd5e9e2f43d18 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Fri, 8 May 2026 16:57:23 -0700 Subject: [PATCH 10/10] ci(release): keep canary to status check --- .github/workflows/release-canary.yml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 3e17ee247..61f8a8a1e 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -29,14 +29,6 @@ jobs: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status - - name: Create sandbox - run: | - openshell sandbox create \ - --no-keep \ - --no-tty \ - --no-auto-providers \ - -- /bin/sh -lc 'echo openshell-canary-sandbox-ok' - ubuntu: name: Ubuntu Docker if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} @@ -59,14 +51,6 @@ jobs: curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status - - name: Create sandbox - run: | - openshell sandbox create \ - --no-keep \ - --no-tty \ - --no-auto-providers \ - -- /bin/sh -lc 'echo openshell-canary-sandbox-ok' - fedora: name: Fedora RPM if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} @@ -87,11 +71,3 @@ jobs: run: | curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh | sh openshell status - - - name: Create sandbox - run: | - openshell sandbox create \ - --no-keep \ - --no-tty \ - --no-auto-providers \ - -- /bin/sh -lc 'echo openshell-canary-sandbox-ok'