From f3a4b9a58ff1e4a252a618ce9498fca21b4bfef3 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 14:24:58 -0700 Subject: [PATCH 1/7] ci(release): simplify release canary --- .github/workflows/release-canary.yml | 292 +++------------------------ 1 file changed, 23 insertions(+), 269 deletions(-) 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 From 3ebd4c1e58ee95e42bb27ee98c686dec9d1db98c Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 16:50:24 -0700 Subject: [PATCH 2/7] fix(release): make dev installer canary ready --- install-dev.sh | 157 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 134 insertions(+), 23 deletions(-) diff --git a/install-dev.sh b/install-dev.sh index edacd2b8d..4e7a3a797 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" } @@ -287,6 +271,49 @@ install_deb_package() { fi } +ensure_local_gateway_env() { + _config_dir="${TARGET_HOME}/.config/openshell" + _env_file="${_config_dir}/gateway.env" + + # shellcheck disable=SC2016 + as_target_user sh -c ' + env_file=$1 + port=$2 + + mkdir -p "$(dirname "$env_file")" + touch "$env_file" + chmod 600 "$env_file" + + add_var() { + key=$1 + value=$2 + if ! grep -Eq "^[[:space:]]*${key}=" "$env_file"; then + printf "%s=%s\n" "$key" "$value" >>"$env_file" + fi + } + + if ! grep -Eq "^[[:space:]]*OPENSHELL_SSH_HANDSHAKE_SECRET=" "$env_file"; then + if command -v openssl >/dev/null 2>&1; then + secret="$(openssl rand -hex 32)" + elif command -v od >/dev/null 2>&1; then + secret="$(od -An -tx1 -N32 /dev/urandom | tr -d " \n")" + else + echo "openshell: error: openssl or od is required to generate OPENSHELL_SSH_HANDSHAKE_SECRET" >&2 + exit 1 + fi + printf "%s=%s\n" OPENSHELL_SSH_HANDSHAKE_SECRET "$secret" >>"$env_file" + fi + + add_var OPENSHELL_BIND_ADDRESS 127.0.0.1 + add_var OPENSHELL_SERVER_PORT "$port" + add_var OPENSHELL_DISABLE_TLS true + add_var OPENSHELL_DISABLE_GATEWAY_AUTH true + add_var OPENSHELL_GRPC_ENDPOINT "http://127.0.0.1:${port}" + add_var OPENSHELL_SSH_GATEWAY_HOST 127.0.0.1 + add_var OPENSHELL_SSH_GATEWAY_PORT "$port" + ' sh "$_env_file" "$LOCAL_GATEWAY_PORT" +} + homebrew_formula_path() { _tap="$1" _formula="$2" @@ -313,15 +340,30 @@ 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() { 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 http://127.0.0.1:17670 --local --name local" + warn "could not reach the user systemd manager for ${TARGET_USER}" + start_direct_gateway + info "registering local gateway as ${TARGET_USER}..." + register_local_gateway + wait_for_registered_gateway return 0 fi @@ -331,6 +373,73 @@ start_user_gateway() { info "registering local gateway as ${TARGET_USER}..." register_local_gateway + wait_for_registered_gateway +} + +start_direct_gateway() { + _gateway_bin="${OPENSHELL_GATEWAY_BIN:-openshell-gateway}" + _config_dir="${TARGET_HOME}/.config/openshell" + _env_file="${_config_dir}/gateway.env" + _state_home="${XDG_STATE_HOME:-${TARGET_HOME}/.local/state}" + _state_dir="${_state_home}/openshell/gateway" + _pid_file="${_state_dir}/openshell-gateway.pid" + _log_file="${_state_dir}/openshell-gateway.log" + + info "starting openshell-gateway directly as ${TARGET_USER}..." + + # shellcheck disable=SC2016 + as_target_user sh -c ' + gateway_bin=$1 + env_file=$2 + state_dir=$3 + pid_file=$4 + log_file=$5 + port=$6 + + if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then + exit 0 + fi + + mkdir -p "$state_dir" + + export OPENSHELL_BIND_ADDRESS="${OPENSHELL_BIND_ADDRESS:-127.0.0.1}" + export OPENSHELL_SERVER_PORT="${OPENSHELL_SERVER_PORT:-$port}" + export OPENSHELL_DISABLE_TLS="${OPENSHELL_DISABLE_TLS:-true}" + export OPENSHELL_DISABLE_GATEWAY_AUTH="${OPENSHELL_DISABLE_GATEWAY_AUTH:-true}" + export OPENSHELL_DB_URL="${OPENSHELL_DB_URL:-sqlite:${state_dir}/openshell.db?mode=rwc}" + export OPENSHELL_GRPC_ENDPOINT="${OPENSHELL_GRPC_ENDPOINT:-http://127.0.0.1:${port}}" + export OPENSHELL_SSH_GATEWAY_HOST="${OPENSHELL_SSH_GATEWAY_HOST:-127.0.0.1}" + export OPENSHELL_SSH_GATEWAY_PORT="${OPENSHELL_SSH_GATEWAY_PORT:-$port}" + + if [ -f "$env_file" ]; then + set -a + . "$env_file" + set +a + fi + + nohup "$gateway_bin" >"$log_file" 2>&1 & + printf "%s\n" "$!" >"$pid_file" + ' sh "$_gateway_bin" "$_env_file" "$_state_dir" "$_pid_file" "$_log_file" "$LOCAL_GATEWAY_PORT" +} + +wait_for_registered_gateway() { + _status_bin="${OPENSHELL_REGISTER_BIN:-openshell}" + _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" + _elapsed=0 + _last_output="" + + info "waiting for local gateway to become reachable..." + while [ "$_elapsed" -lt "$_timeout" ]; do + if _last_output="$(as_target_user "$_status_bin" status 2>&1)"; then + info "local gateway is reachable" + return 0 + fi + sleep 1 + _elapsed=$((_elapsed + 1)) + done + + printf '%s\n' "$_last_output" >&2 + error "local gateway did not become reachable within ${_timeout}s" } remove_local_gateway_registration() { @@ -415,6 +524,7 @@ install_linux_deb() { info "installing ${_deb_file}..." install_deb_package "$_deb_path" info "installed ${APP_NAME} package from ${RELEASE_TAG}" + ensure_local_gateway_env start_user_gateway } @@ -468,6 +578,7 @@ install_macos_homebrew() { info "registering local gateway as ${TARGET_USER}..." register_local_gateway + wait_for_registered_gateway } main() { From c87344127dd3946e7070c955660a41abaead4c59 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 20:02:13 -0700 Subject: [PATCH 3/7] fix(release): set mac homebrew canary driver --- python/openshell/release_formula_test.py | 4 ++-- tasks/scripts/release.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/openshell/release_formula_test.py b/python/openshell/release_formula_test.py index 56abd7af6..3138ec6a9 100644 --- a/python/openshell/release_formula_test.py +++ b/python/openshell/release_formula_test.py @@ -8,7 +8,7 @@ from pathlib import Path -def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_without_default_driver( +def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_and_vm_driver( tmp_path: Path, ) -> None: release_dir = tmp_path / "release" @@ -51,7 +51,7 @@ def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_without_defaul "v0.0.10/openshell-driver-vm-aarch64-apple-darwin.tar.gz" ) in formula assert 'sha256 "' + "b" * 64 + '"' in formula - assert "OPENSHELL_DRIVERS" not in formula + assert 'OPENSHELL_DRIVERS: "vm"' in formula assert 'OPENSHELL_DRIVER_DIR: "#{opt_libexec}"' in formula assert "entitlements.atomic_write" in formula assert "brew services restart openshell" in formula diff --git a/tasks/scripts/release.py b/tasks/scripts/release.py index 0a6893121..f80a0c1dc 100644 --- a/tasks/scripts/release.py +++ b/tasks/scripts/release.py @@ -288,6 +288,7 @@ def post_install service do run opt_bin/"openshell-gateway" environment_variables( + OPENSHELL_DRIVERS: "vm", OPENSHELL_BIND_ADDRESS: "127.0.0.1", OPENSHELL_SERVER_PORT: "{LOCAL_GATEWAY_PORT}", OPENSHELL_DISABLE_TLS: "true", From d346cafba1af791740cee6b56fc18a0b85a32c77 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:25:43 -0700 Subject: [PATCH 4/7] fix(installer): remove direct gateway fallback --- install-dev.sh | 120 ++----------------------------------------------- 1 file changed, 3 insertions(+), 117 deletions(-) diff --git a/install-dev.sh b/install-dev.sh index 4e7a3a797..7f38485aa 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -271,49 +271,6 @@ install_deb_package() { fi } -ensure_local_gateway_env() { - _config_dir="${TARGET_HOME}/.config/openshell" - _env_file="${_config_dir}/gateway.env" - - # shellcheck disable=SC2016 - as_target_user sh -c ' - env_file=$1 - port=$2 - - mkdir -p "$(dirname "$env_file")" - touch "$env_file" - chmod 600 "$env_file" - - add_var() { - key=$1 - value=$2 - if ! grep -Eq "^[[:space:]]*${key}=" "$env_file"; then - printf "%s=%s\n" "$key" "$value" >>"$env_file" - fi - } - - if ! grep -Eq "^[[:space:]]*OPENSHELL_SSH_HANDSHAKE_SECRET=" "$env_file"; then - if command -v openssl >/dev/null 2>&1; then - secret="$(openssl rand -hex 32)" - elif command -v od >/dev/null 2>&1; then - secret="$(od -An -tx1 -N32 /dev/urandom | tr -d " \n")" - else - echo "openshell: error: openssl or od is required to generate OPENSHELL_SSH_HANDSHAKE_SECRET" >&2 - exit 1 - fi - printf "%s=%s\n" OPENSHELL_SSH_HANDSHAKE_SECRET "$secret" >>"$env_file" - fi - - add_var OPENSHELL_BIND_ADDRESS 127.0.0.1 - add_var OPENSHELL_SERVER_PORT "$port" - add_var OPENSHELL_DISABLE_TLS true - add_var OPENSHELL_DISABLE_GATEWAY_AUTH true - add_var OPENSHELL_GRPC_ENDPOINT "http://127.0.0.1:${port}" - add_var OPENSHELL_SSH_GATEWAY_HOST 127.0.0.1 - add_var OPENSHELL_SSH_GATEWAY_PORT "$port" - ' sh "$_env_file" "$LOCAL_GATEWAY_PORT" -} - homebrew_formula_path() { _tap="$1" _formula="$2" @@ -359,11 +316,9 @@ start_user_gateway() { info "restarting openshell-gateway user service as ${TARGET_USER}..." if ! as_target_user systemctl --user daemon-reload; then - warn "could not reach the user systemd manager for ${TARGET_USER}" - start_direct_gateway - info "registering local gateway as ${TARGET_USER}..." - register_local_gateway - wait_for_registered_gateway + 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 @@ -373,73 +328,6 @@ start_user_gateway() { info "registering local gateway as ${TARGET_USER}..." register_local_gateway - wait_for_registered_gateway -} - -start_direct_gateway() { - _gateway_bin="${OPENSHELL_GATEWAY_BIN:-openshell-gateway}" - _config_dir="${TARGET_HOME}/.config/openshell" - _env_file="${_config_dir}/gateway.env" - _state_home="${XDG_STATE_HOME:-${TARGET_HOME}/.local/state}" - _state_dir="${_state_home}/openshell/gateway" - _pid_file="${_state_dir}/openshell-gateway.pid" - _log_file="${_state_dir}/openshell-gateway.log" - - info "starting openshell-gateway directly as ${TARGET_USER}..." - - # shellcheck disable=SC2016 - as_target_user sh -c ' - gateway_bin=$1 - env_file=$2 - state_dir=$3 - pid_file=$4 - log_file=$5 - port=$6 - - if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then - exit 0 - fi - - mkdir -p "$state_dir" - - export OPENSHELL_BIND_ADDRESS="${OPENSHELL_BIND_ADDRESS:-127.0.0.1}" - export OPENSHELL_SERVER_PORT="${OPENSHELL_SERVER_PORT:-$port}" - export OPENSHELL_DISABLE_TLS="${OPENSHELL_DISABLE_TLS:-true}" - export OPENSHELL_DISABLE_GATEWAY_AUTH="${OPENSHELL_DISABLE_GATEWAY_AUTH:-true}" - export OPENSHELL_DB_URL="${OPENSHELL_DB_URL:-sqlite:${state_dir}/openshell.db?mode=rwc}" - export OPENSHELL_GRPC_ENDPOINT="${OPENSHELL_GRPC_ENDPOINT:-http://127.0.0.1:${port}}" - export OPENSHELL_SSH_GATEWAY_HOST="${OPENSHELL_SSH_GATEWAY_HOST:-127.0.0.1}" - export OPENSHELL_SSH_GATEWAY_PORT="${OPENSHELL_SSH_GATEWAY_PORT:-$port}" - - if [ -f "$env_file" ]; then - set -a - . "$env_file" - set +a - fi - - nohup "$gateway_bin" >"$log_file" 2>&1 & - printf "%s\n" "$!" >"$pid_file" - ' sh "$_gateway_bin" "$_env_file" "$_state_dir" "$_pid_file" "$_log_file" "$LOCAL_GATEWAY_PORT" -} - -wait_for_registered_gateway() { - _status_bin="${OPENSHELL_REGISTER_BIN:-openshell}" - _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" - _elapsed=0 - _last_output="" - - info "waiting for local gateway to become reachable..." - while [ "$_elapsed" -lt "$_timeout" ]; do - if _last_output="$(as_target_user "$_status_bin" status 2>&1)"; then - info "local gateway is reachable" - return 0 - fi - sleep 1 - _elapsed=$((_elapsed + 1)) - done - - printf '%s\n' "$_last_output" >&2 - error "local gateway did not become reachable within ${_timeout}s" } remove_local_gateway_registration() { @@ -524,7 +412,6 @@ install_linux_deb() { info "installing ${_deb_file}..." install_deb_package "$_deb_path" info "installed ${APP_NAME} package from ${RELEASE_TAG}" - ensure_local_gateway_env start_user_gateway } @@ -578,7 +465,6 @@ install_macos_homebrew() { info "registering local gateway as ${TARGET_USER}..." register_local_gateway - wait_for_registered_gateway } main() { From ce465567c1a8a8edd35a695d25a2bb94902028e4 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:34:01 -0700 Subject: [PATCH 5/7] fix(installer): wait for gateway readiness --- install-dev.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/install-dev.sh b/install-dev.sh index 7f38485aa..9f41fab07 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -326,10 +326,31 @@ 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 info "registering local gateway as ${TARGET_USER}..." register_local_gateway } +wait_for_local_gateway() { + _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" + _elapsed=0 + _last_output="" + _health_url="http://127.0.0.1:${LOCAL_GATEWAY_PORT}/healthz" + + info "waiting for local gateway to become reachable..." + while [ "$_elapsed" -lt "$_timeout" ]; do + if _last_output="$(as_target_user curl -fsS --max-time 2 "$_health_url" 2>&1 >/dev/null)"; then + info "local gateway is reachable" + return 0 + fi + sleep 1 + _elapsed=$((_elapsed + 1)) + done + + printf '%s\n' "$_last_output" >&2 + error "local gateway did not become reachable at ${_health_url} within ${_timeout}s" +} + remove_local_gateway_registration() { [ -n "$TARGET_HOME" ] || error "cannot resolve home directory for ${TARGET_USER}" _config_dir="${TARGET_HOME}/.config/openshell" @@ -463,6 +484,7 @@ install_macos_homebrew() { OPENSHELL_REGISTER_BIN="${_brew_prefix}/bin/openshell" fi + wait_for_local_gateway info "registering local gateway as ${TARGET_USER}..." register_local_gateway } From 0aa17406112c9549494a4d0e02e00c7792322761 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:39:35 -0700 Subject: [PATCH 6/7] fix(installer): verify gateway status readiness --- install-dev.sh | 54 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/install-dev.sh b/install-dev.sh index 9f41fab07..a55fcbce8 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -326,21 +326,22 @@ 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 + wait_for_local_gateway_listener info "registering local gateway as ${TARGET_USER}..." register_local_gateway + wait_for_local_gateway_status } -wait_for_local_gateway() { +wait_for_local_gateway_listener() { _timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}" _elapsed=0 _last_output="" - _health_url="http://127.0.0.1:${LOCAL_GATEWAY_PORT}/healthz" + _probe_url="http://127.0.0.1:${LOCAL_GATEWAY_PORT}/" - info "waiting for local gateway to become reachable..." + info "waiting for local gateway listener to become reachable..." while [ "$_elapsed" -lt "$_timeout" ]; do - if _last_output="$(as_target_user curl -fsS --max-time 2 "$_health_url" 2>&1 >/dev/null)"; then - info "local gateway is reachable" + 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 @@ -348,7 +349,31 @@ wait_for_local_gateway() { done printf '%s\n' "$_last_output" >&2 - error "local gateway did not become reachable at ${_health_url} within ${_timeout}s" + 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() { @@ -372,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=$? @@ -391,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 @@ -484,9 +519,10 @@ install_macos_homebrew() { OPENSHELL_REGISTER_BIN="${_brew_prefix}/bin/openshell" fi - wait_for_local_gateway + wait_for_local_gateway_listener info "registering local gateway as ${TARGET_USER}..." register_local_gateway + wait_for_local_gateway_status } main() { From 39ceaf5680d04099cf99cfc960b3cfec418859e7 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Thu, 7 May 2026 21:52:03 -0700 Subject: [PATCH 7/7] fix(release): use default homebrew driver --- python/openshell/release_formula_test.py | 4 ++-- tasks/scripts/release.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/python/openshell/release_formula_test.py b/python/openshell/release_formula_test.py index 3138ec6a9..56abd7af6 100644 --- a/python/openshell/release_formula_test.py +++ b/python/openshell/release_formula_test.py @@ -8,7 +8,7 @@ from pathlib import Path -def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_and_vm_driver( +def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_without_default_driver( tmp_path: Path, ) -> None: release_dir = tmp_path / "release" @@ -51,7 +51,7 @@ def test_generate_homebrew_formula_uses_tagged_macos_driver_asset_and_vm_driver( "v0.0.10/openshell-driver-vm-aarch64-apple-darwin.tar.gz" ) in formula assert 'sha256 "' + "b" * 64 + '"' in formula - assert 'OPENSHELL_DRIVERS: "vm"' in formula + assert "OPENSHELL_DRIVERS" not in formula assert 'OPENSHELL_DRIVER_DIR: "#{opt_libexec}"' in formula assert "entitlements.atomic_write" in formula assert "brew services restart openshell" in formula diff --git a/tasks/scripts/release.py b/tasks/scripts/release.py index f80a0c1dc..0a6893121 100644 --- a/tasks/scripts/release.py +++ b/tasks/scripts/release.py @@ -288,7 +288,6 @@ def post_install service do run opt_bin/"openshell-gateway" environment_variables( - OPENSHELL_DRIVERS: "vm", OPENSHELL_BIND_ADDRESS: "127.0.0.1", OPENSHELL_SERVER_PORT: "{LOCAL_GATEWAY_PORT}", OPENSHELL_DISABLE_TLS: "true",