diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index defe6f32a..415a4f597 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -2,293 +2,47 @@ name: Release Canary on: workflow_dispatch: - inputs: - tag: - description: "Release tag to test (e.g. dev, v1.2.3)" - required: true - type: string workflow_run: - workflows: ["Release Dev", "Release Tag"] + workflows: ["Release Dev"] types: [completed] permissions: contents: read - packages: read defaults: run: shell: bash jobs: - # --------------------------------------------------------------------------- - # Verify the default install path (no OPENSHELL_VERSION) resolves to latest - # --------------------------------------------------------------------------- - install-default: - name: Install default (${{ matrix.arch }}) - if: >- - github.event.workflow_run.conclusion == 'success' - && github.event.workflow_run.name == 'Release Tag' - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - - arch: arm64 - runner: linux-arm64-cpu8 - runs-on: ${{ matrix.runner }} - timeout-minutes: 10 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Install CLI (default / latest) - run: | - set -euo pipefail - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh - - - name: Verify CLI installation - run: | - set -euo pipefail - command -v openshell - ACTUAL="$(openshell --version)" - echo "Installed: $ACTUAL" - # This job only runs after Release Tag, so the triggering tag - # should match the latest release the default installer resolves to. - TAG="${{ github.event.workflow_run.head_branch }}" - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - EXPECTED="${TAG#v}" - if [[ "$ACTUAL" != *"$EXPECTED"* ]]; then - echo "::error::Version mismatch: expected '$EXPECTED' in '$ACTUAL'" - exit 1 - fi - echo "Version check passed: found $EXPECTED in output" - fi - - install-dev: - name: Install Debian package (${{ matrix.arch }}) + macos: + name: macOS Homebrew if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - - arch: arm64 - runner: linux-arm64-cpu8 - runs-on: ${{ matrix.runner }} - timeout-minutes: 10 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + runs-on: macos-latest-xlarge + timeout-minutes: 20 steps: - - name: Determine release tag - id: release + - name: Install dev and check status run: | - set -euo pipefail - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" - else - WORKFLOW_NAME="${{ github.event.workflow_run.name }}" - if [ "$WORKFLOW_NAME" = "Release Dev" ]; then - echo "tag=dev" >> "$GITHUB_OUTPUT" - elif [ "$WORKFLOW_NAME" = "Release Tag" ]; then - TAG="${{ github.event.workflow_run.head_branch }}" - if [ -z "$TAG" ]; then - echo "::error::Could not determine release tag from workflow_run" - exit 1 - fi - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - else - echo "::error::Unexpected triggering workflow: ${WORKFLOW_NAME}" - exit 1 - fi - fi + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install-dev.sh | sh + openshell status - - name: Install Debian package - run: | - set -euo pipefail - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install-dev.sh \ - | OPENSHELL_VERSION=${{ steps.release.outputs.tag }} sh - - - name: Verify gateway and VM driver versions - run: | - set -euo pipefail - command -v openshell-gateway - test -x /usr/libexec/openshell/openshell-driver-vm - - GATEWAY_ACTUAL="$(openshell-gateway --version)" - DRIVER_ACTUAL="$(/usr/libexec/openshell/openshell-driver-vm --version)" - echo "Gateway: ${GATEWAY_ACTUAL}" - echo "Driver: ${DRIVER_ACTUAL}" - - TAG="${{ steps.release.outputs.tag }}" - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - EXPECTED="${TAG#v}" - for actual in "$GATEWAY_ACTUAL" "$DRIVER_ACTUAL"; do - if [[ "$actual" != *"$EXPECTED"* ]]; then - echo "::error::Version mismatch: expected '$EXPECTED' in '$actual'" - exit 1 - fi - done - echo "Version check passed: found $EXPECTED in both binaries" - else - echo "Non-release tag ($TAG), skipping version check" - fi - - canary: - name: Canary package gateway (${{ matrix.arch }}) + ubuntu: + name: Ubuntu Docker if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - - arch: arm64 - runner: linux-arm64-cpu8 - runs-on: ${{ matrix.runner }} - timeout-minutes: 30 - env: - OPENSHELL_REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_CANARY_PORT: "17670" + runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v6 - - - name: Determine release tag - id: release + - name: Ensure Docker run: | - set -euo pipefail - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" - else - WORKFLOW_NAME="${{ github.event.workflow_run.name }}" - if [ "$WORKFLOW_NAME" = "Release Dev" ]; then - echo "tag=dev" >> "$GITHUB_OUTPUT" - elif [ "$WORKFLOW_NAME" = "Release Tag" ]; then - TAG="${{ github.event.workflow_run.head_branch }}" - if [ -z "$TAG" ]; then - echo "::error::Could not determine release tag from workflow_run" - exit 1 - fi - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - else - echo "::error::Unexpected triggering workflow: ${WORKFLOW_NAME}" - exit 1 - fi + if ! command -v docker >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y docker.io fi + sudo systemctl start docker || sudo service docker start + mkdir -p "${HOME}/.config/openshell" + printf 'OPENSHELL_DRIVERS=docker\n' > "${HOME}/.config/openshell/gateway.env" + docker info - - name: Install Debian package + - name: Install dev and check status run: | - set -euo pipefail - curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install-dev.sh \ - | OPENSHELL_VERSION=${{ steps.release.outputs.tag }} sh - - - name: Verify package binaries - run: | - set -euo pipefail - command -v openshell - command -v openshell-gateway - test -x /usr/libexec/openshell/openshell-driver-vm - - CLI_ACTUAL="$(openshell --version)" - GATEWAY_ACTUAL="$(openshell-gateway --version)" - DRIVER_ACTUAL="$(/usr/libexec/openshell/openshell-driver-vm --version)" - echo "CLI: ${CLI_ACTUAL}" - echo "Gateway: ${GATEWAY_ACTUAL}" - echo "Driver: ${DRIVER_ACTUAL}" - - TAG="${{ steps.release.outputs.tag }}" - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - EXPECTED="${TAG#v}" - for actual in "$CLI_ACTUAL" "$GATEWAY_ACTUAL" "$DRIVER_ACTUAL"; do - if [[ "$actual" != *"$EXPECTED"* ]]; then - echo "::error::Version mismatch: expected '$EXPECTED' in '$actual'" - exit 1 - fi - done - echo "Version check passed: found $EXPECTED in package binaries" - else - echo "Non-release tag ($TAG), skipping version check" - fi - - - name: Start packaged gateway - run: | - set -euo pipefail - - # The CLI no longer owns gateway lifecycle. In CI we start the - # gateway binary installed by the Debian package directly, using the - # Docker driver so this canary can launch a real sandbox. - systemctl --user stop openshell-gateway >/dev/null 2>&1 || true - - STATE_DIR="$(mktemp -d)" - LOG="${STATE_DIR}/openshell-gateway.log" - echo "GATEWAY_LOG=${LOG}" >> "$GITHUB_ENV" - echo "GATEWAY_STATE_DIR=${STATE_DIR}" >> "$GITHUB_ENV" - - OPENSHELL_BIND_ADDRESS=0.0.0.0 \ - OPENSHELL_SERVER_PORT="${OPENSHELL_CANARY_PORT}" \ - OPENSHELL_DISABLE_TLS=true \ - OPENSHELL_DISABLE_GATEWAY_AUTH=true \ - OPENSHELL_DRIVERS=docker \ - OPENSHELL_DB_URL="sqlite:${STATE_DIR}/openshell.db?mode=rwc" \ - OPENSHELL_GRPC_ENDPOINT="http://host.openshell.internal:${OPENSHELL_CANARY_PORT}" \ - OPENSHELL_SSH_GATEWAY_HOST=127.0.0.1 \ - OPENSHELL_SSH_GATEWAY_PORT="${OPENSHELL_CANARY_PORT}" \ - OPENSHELL_SANDBOX_NAMESPACE="canary-${{ matrix.arch }}-${{ github.run_id }}" \ - nohup openshell-gateway >"${LOG}" 2>&1 & - PID=$! - echo "GATEWAY_PID=${PID}" >> "$GITHUB_ENV" - - for _ in $(seq 1 60); do - if curl -fsS "http://127.0.0.1:${OPENSHELL_CANARY_PORT}/healthz" >/dev/null; then - break - fi - if ! kill -0 "$PID" 2>/dev/null; then - echo "::error::openshell-gateway exited before becoming healthy" - cat "$LOG" - exit 1 - fi - sleep 1 - done - - curl -fsS "http://127.0.0.1:${OPENSHELL_CANARY_PORT}/healthz" - openshell gateway remove local >/dev/null 2>&1 || true - openshell gateway add "http://127.0.0.1:${OPENSHELL_CANARY_PORT}" --local --name local - - - name: Run canary test - run: | - set -euo pipefail - - echo "Creating sandbox and running 'echo hello world'..." - OUTPUT=$(openshell sandbox create --no-keep --no-tty -- echo "hello world" 2>&1) || { - EXIT_CODE=$? - echo "::error::openshell sandbox create failed with exit code ${EXIT_CODE}" - echo "$OUTPUT" - exit $EXIT_CODE - } - - echo "$OUTPUT" - - if echo "$OUTPUT" | grep -q "hello world"; then - echo "Canary test passed: 'hello world' found in output" - else - echo "::error::Canary test failed: 'hello world' not found in output" - exit 1 - fi - - - name: Stop packaged gateway - if: always() - run: | - set -euo pipefail - if [ -n "${GATEWAY_PID:-}" ]; then - kill "$GATEWAY_PID" >/dev/null 2>&1 || true - fi - if [ "${{ job.status }}" != "success" ] && [ -n "${GATEWAY_LOG:-}" ] && [ -f "$GATEWAY_LOG" ]; then - echo "Gateway log:" - cat "$GATEWAY_LOG" - fi + curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install-dev.sh | sh + openshell status diff --git a/install-dev.sh b/install-dev.sh index edacd2b8d..a55fcbce8 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -227,26 +227,10 @@ find_deb_asset() { _arch="$2" awk -v arch="$_arch" ' - { - name = $2 - sub("^\\*", "", name) - - if (name == "openshell-dev-" arch ".deb") { - selected = name - found = 1 - exit - } - - if (fallback == "" && name ~ "^openshell_.*_" arch "\\.deb$") { - fallback = name - } - } - END { - if (found) { - print selected - } else if (fallback != "") { - print fallback - } + $2 ~ "^\\*?openshell[-_].*[-_]" arch "\\.deb$" { + sub("^\\*", "", $2) + print $2 + exit } ' "$_checksums" } @@ -313,6 +297,19 @@ patch_homebrew_formula() { sed 's/entitlements\.write <<~XML/entitlements.atomic_write <<~XML/' "$_formula_file" >"$_patched_file" 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() { @@ -329,8 +326,54 @@ 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_status +} + +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 + + 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() { @@ -354,7 +397,7 @@ 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" ] || printf '%s\n' "$_add_output" >&2 + [ -z "$_add_output" ] || print_gateway_add_output "$_add_output" return 0 else _add_status=$? @@ -373,6 +416,16 @@ register_local_gateway() { 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_platform @@ -466,8 +519,10 @@ 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_status } main() {