From 7194b6c0f1f4ab502e69f3efeb840e313e8fd185 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Wed, 6 May 2026 16:52:47 -0700 Subject: [PATCH 1/4] fix(installer): install dev formula from Homebrew tap Signed-off-by: Drew Newberry --- .github/workflows/test-install.yml | 9 +- crates/openshell-driver-vm/README.md | 10 +- e2e/install-dev/macos_homebrew_test.sh | 135 +++++++++++++++++++++++++ install-dev.sh | 34 ++++++- 4 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 e2e/install-dev/macos_homebrew_test.sh diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml index 06b1e007f..f867e4d56 100644 --- a/.github/workflows/test-install.yml +++ b/.github/workflows/test-install.yml @@ -1,16 +1,20 @@ -name: Test Install Script +name: Test Install Scripts on: pull_request: paths: - 'install.sh' + - 'install-dev.sh' - 'e2e/install/**' + - 'e2e/install-dev/**' - '.github/workflows/test-install.yml' push: branches: [main] paths: - 'install.sh' + - 'install-dev.sh' - 'e2e/install/**' + - 'e2e/install-dev/**' - '.github/workflows/test-install.yml' workflow_dispatch: @@ -39,6 +43,9 @@ jobs: test: e2e/install/fish_test.fish run: fish e2e/install/fish_test.fish install: fish + - shell: install-dev-macos + test: e2e/install-dev/macos_homebrew_test.sh + run: sh e2e/install-dev/macos_homebrew_test.sh steps: - uses: actions/checkout@v6 diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index d90d4b843..cb11f8db1 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -182,11 +182,11 @@ On Linux amd64 and arm64, `install-dev.sh` installs the Debian package from the selected `OPENSHELL_VERSION` release tag. That package includes `openshell-gateway` and `openshell-driver-vm`. -On Apple Silicon macOS, `install-dev.sh` installs the generated `openshell.rb` -formula from the selected release. Homebrew installs `openshell`, -`openshell-gateway`, and `openshell-driver-vm`, ad-hoc signs the driver with -the Hypervisor entitlement in `post_install`, and owns the `brew services` -gateway lifecycle. +On Apple Silicon macOS, `install-dev.sh` stages the generated `openshell.rb` +formula from the selected release in a local `nvidia/openshell-dev` Homebrew +tap. Homebrew installs `openshell`, `openshell-gateway`, and +`openshell-driver-vm`, ad-hoc signs the driver with the Hypervisor entitlement +in `post_install`, and owns the `brew services` gateway lifecycle. ## Relationship to `openshell-vm` diff --git a/e2e/install-dev/macos_homebrew_test.sh b/e2e/install-dev/macos_homebrew_test.sh new file mode 100644 index 000000000..e7f526dc3 --- /dev/null +++ b/e2e/install-dev/macos_homebrew_test.sh @@ -0,0 +1,135 @@ +#!/bin/sh +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Fake-Homebrew regression test for install-dev.sh on macOS. + +set -eu + +TEST_DIR="$(mktemp -d)" +trap 'rm -rf "$TEST_DIR"' EXIT + +REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +FAKE_BIN="${TEST_DIR}/bin" +BREW_TAP_DIR="${TEST_DIR}/homebrew/Library/Taps/nvidia/homebrew-openshell-dev" +BREW_PREFIX="${TEST_DIR}/prefix" +BREW_LOG="${TEST_DIR}/brew.log" +INSTALL_OUTPUT="${TEST_DIR}/install.out" + +mkdir -p "$FAKE_BIN" "$BREW_PREFIX/bin" "${TEST_DIR}/home" +: >"$BREW_LOG" + +cat >"${FAKE_BIN}/uname" <<'EOF' +#!/bin/sh +case "$1" in + -s) + echo Darwin + ;; + -m) + echo arm64 + ;; + *) + /usr/bin/uname "$@" + ;; +esac +EOF +chmod +x "${FAKE_BIN}/uname" + +cat >"${FAKE_BIN}/curl" <<'EOF' +#!/bin/sh +output= +while [ "$#" -gt 0 ]; do + case "$1" in + -o) + shift + output="$1" + ;; + esac + shift +done + +[ -n "$output" ] || exit 2 +cat >"$output" <<'FORMULA' +class Openshell < Formula +end +FORMULA +EOF +chmod +x "${FAKE_BIN}/curl" + +cat >"${FAKE_BIN}/brew" <<'EOF' +#!/bin/sh +printf 'brew %s\n' "$*" >>"$BREW_LOG" + +case "$1" in + --version) + echo "Homebrew 5.1.9" + ;; + tap-info) + exit 1 + ;; + tap-new) + mkdir -p "${BREW_TAP_DIR}/Formula" + ;; + --repository) + echo "$BREW_TAP_DIR" + ;; + list) + exit 1 + ;; + install|reinstall) + ;; + services) + ;; + --prefix) + echo "$BREW_PREFIX" + ;; + *) + echo "unexpected brew command: $*" >&2 + exit 99 + ;; +esac +EOF +chmod +x "${FAKE_BIN}/brew" + +cat >"${BREW_PREFIX}/bin/openshell" <<'EOF' +#!/bin/sh +printf 'openshell %s\n' "$*" >>"$BREW_LOG" +EOF +chmod +x "${BREW_PREFIX}/bin/openshell" + +PATH="${FAKE_BIN}:/usr/bin:/bin:/usr/sbin:/sbin" \ + HOME="${TEST_DIR}/home" \ + BREW_LOG="$BREW_LOG" \ + BREW_TAP_DIR="$BREW_TAP_DIR" \ + BREW_PREFIX="$BREW_PREFIX" \ + sh "${REPO_ROOT}/install-dev.sh" >"$INSTALL_OUTPUT" 2>&1 + +assert_log_contains() { + _needle="$1" + if ! grep -qF "$_needle" "$BREW_LOG"; then + echo "missing log entry: $_needle" >&2 + echo "--- brew log ---" >&2 + cat "$BREW_LOG" >&2 + echo "--- installer output ---" >&2 + cat "$INSTALL_OUTPUT" >&2 + exit 1 + fi +} + +assert_log_contains "brew tap-new --no-git nvidia/openshell-dev" +assert_log_contains "brew install --formula nvidia/openshell-dev/openshell" +assert_log_contains "brew services restart openshell" +assert_log_contains "openshell gateway add http://127.0.0.1:17670 --local --name local" + +if ! grep -qF "class Openshell < Formula" "${BREW_TAP_DIR}/Formula/openshell.rb"; then + echo "formula was not staged into the local tap" >&2 + exit 1 +fi + +if grep -Eq 'brew (install|reinstall) --formula .*[.]rb' "$BREW_LOG"; then + echo "installer still passed a formula file path to Homebrew" >&2 + cat "$BREW_LOG" >&2 + exit 1 +fi + +echo "install-dev macOS Homebrew test passed" diff --git a/install-dev.sh b/install-dev.sh index f53c99bed..8fd46df36 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -16,6 +16,8 @@ GITHUB_URL="https://github.com/${REPO}" RELEASE_TAG="${OPENSHELL_VERSION:-dev}" CHECKSUMS_NAME="openshell-checksums-sha256.txt" LOCAL_GATEWAY_PORT="17670" +HOMEBREW_DEV_TAP="nvidia/openshell-dev" +HOMEBREW_FORMULA_NAME="openshell" info() { printf '%s: %s\n' "$APP_NAME" "$*" >&2 @@ -269,6 +271,23 @@ install_deb_package() { 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" +} + start_user_gateway() { info "restarting openshell-gateway user service as ${TARGET_USER}..." @@ -386,13 +405,24 @@ install_macos_homebrew() { 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" + + _tap_formula_file="$(homebrew_formula_path "$HOMEBREW_DEV_TAP" "$HOMEBREW_FORMULA_NAME")" + info "staging Homebrew formula in local tap ${HOMEBREW_DEV_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_DEV_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_file" + as_target_user brew reinstall --formula "$_formula_ref" else info "installing OpenShell with Homebrew..." - as_target_user brew install --formula "$_formula_file" + as_target_user brew install --formula "$_formula_ref" fi info "restarting OpenShell Homebrew service..." From 73ea2e0ee8059c9d9bf8e29e2f813a946cb8189a Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Wed, 6 May 2026 17:16:55 -0700 Subject: [PATCH 2/4] fix(installer): make dev formula postinstall idempotent Signed-off-by: Drew Newberry --- e2e/install-dev/macos_homebrew_test.sh | 15 +++++++++++++++ install-dev.sh | 12 ++++++++++++ python/openshell/release_formula_test.py | 1 + tasks/scripts/release.py | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/e2e/install-dev/macos_homebrew_test.sh b/e2e/install-dev/macos_homebrew_test.sh index e7f526dc3..5b83428d8 100644 --- a/e2e/install-dev/macos_homebrew_test.sh +++ b/e2e/install-dev/macos_homebrew_test.sh @@ -51,6 +51,11 @@ done [ -n "$output" ] || exit 2 cat >"$output" <<'FORMULA' class Openshell < Formula + def post_install + entitlements.write <<~XML + + XML + end end FORMULA EOF @@ -126,6 +131,16 @@ if ! grep -qF "class Openshell < Formula" "${BREW_TAP_DIR}/Formula/openshell.rb" exit 1 fi +if ! grep -qF "entitlements.atomic_write <<~XML" "${BREW_TAP_DIR}/Formula/openshell.rb"; then + echo "formula postinstall was not patched to use atomic_write" >&2 + exit 1 +fi + +if grep -qF "entitlements.write <<~XML" "${BREW_TAP_DIR}/Formula/openshell.rb"; then + echo "formula postinstall still uses non-idempotent write" >&2 + exit 1 +fi + if grep -Eq 'brew (install|reinstall) --formula .*[.]rb' "$BREW_LOG"; then echo "installer still passed a formula file path to Homebrew" >&2 cat "$BREW_LOG" >&2 diff --git a/install-dev.sh b/install-dev.sh index 8fd46df36..936b7f8f3 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -288,6 +288,17 @@ homebrew_formula_path() { 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}..." @@ -406,6 +417,7 @@ install_macos_homebrew() { 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_DEV_TAP" "$HOMEBREW_FORMULA_NAME")" info "staging Homebrew formula in local tap ${HOMEBREW_DEV_TAP}..." diff --git a/python/openshell/release_formula_test.py b/python/openshell/release_formula_test.py index 414cbe199..d3f983e96 100644 --- a/python/openshell/release_formula_test.py +++ b/python/openshell/release_formula_test.py @@ -52,4 +52,5 @@ def test_generate_homebrew_formula_uses_tagged_macos_driver_asset( ) in formula assert 'sha256 "' + "b" * 64 + '"' 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 d1bfc2b2c..4e839bbf0 100644 --- a/tasks/scripts/release.py +++ b/tasks/scripts/release.py @@ -271,7 +271,7 @@ def post_install (var/"log/openshell").mkpath entitlements = var/"openshell/openshell-driver-vm.entitlements.plist" - entitlements.write <<~XML + entitlements.atomic_write <<~XML From 2ca7904dd88f235fe2194f1b5ca12281a73652f1 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Wed, 6 May 2026 17:20:47 -0700 Subject: [PATCH 3/4] chore(ci): drop install-dev smoke wiring Signed-off-by: Drew Newberry --- .github/workflows/test-install.yml | 9 +- e2e/install-dev/macos_homebrew_test.sh | 150 ------------------------- 2 files changed, 1 insertion(+), 158 deletions(-) delete mode 100644 e2e/install-dev/macos_homebrew_test.sh diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml index f867e4d56..06b1e007f 100644 --- a/.github/workflows/test-install.yml +++ b/.github/workflows/test-install.yml @@ -1,20 +1,16 @@ -name: Test Install Scripts +name: Test Install Script on: pull_request: paths: - 'install.sh' - - 'install-dev.sh' - 'e2e/install/**' - - 'e2e/install-dev/**' - '.github/workflows/test-install.yml' push: branches: [main] paths: - 'install.sh' - - 'install-dev.sh' - 'e2e/install/**' - - 'e2e/install-dev/**' - '.github/workflows/test-install.yml' workflow_dispatch: @@ -43,9 +39,6 @@ jobs: test: e2e/install/fish_test.fish run: fish e2e/install/fish_test.fish install: fish - - shell: install-dev-macos - test: e2e/install-dev/macos_homebrew_test.sh - run: sh e2e/install-dev/macos_homebrew_test.sh steps: - uses: actions/checkout@v6 diff --git a/e2e/install-dev/macos_homebrew_test.sh b/e2e/install-dev/macos_homebrew_test.sh deleted file mode 100644 index 5b83428d8..000000000 --- a/e2e/install-dev/macos_homebrew_test.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Fake-Homebrew regression test for install-dev.sh on macOS. - -set -eu - -TEST_DIR="$(mktemp -d)" -trap 'rm -rf "$TEST_DIR"' EXIT - -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -FAKE_BIN="${TEST_DIR}/bin" -BREW_TAP_DIR="${TEST_DIR}/homebrew/Library/Taps/nvidia/homebrew-openshell-dev" -BREW_PREFIX="${TEST_DIR}/prefix" -BREW_LOG="${TEST_DIR}/brew.log" -INSTALL_OUTPUT="${TEST_DIR}/install.out" - -mkdir -p "$FAKE_BIN" "$BREW_PREFIX/bin" "${TEST_DIR}/home" -: >"$BREW_LOG" - -cat >"${FAKE_BIN}/uname" <<'EOF' -#!/bin/sh -case "$1" in - -s) - echo Darwin - ;; - -m) - echo arm64 - ;; - *) - /usr/bin/uname "$@" - ;; -esac -EOF -chmod +x "${FAKE_BIN}/uname" - -cat >"${FAKE_BIN}/curl" <<'EOF' -#!/bin/sh -output= -while [ "$#" -gt 0 ]; do - case "$1" in - -o) - shift - output="$1" - ;; - esac - shift -done - -[ -n "$output" ] || exit 2 -cat >"$output" <<'FORMULA' -class Openshell < Formula - def post_install - entitlements.write <<~XML - - XML - end -end -FORMULA -EOF -chmod +x "${FAKE_BIN}/curl" - -cat >"${FAKE_BIN}/brew" <<'EOF' -#!/bin/sh -printf 'brew %s\n' "$*" >>"$BREW_LOG" - -case "$1" in - --version) - echo "Homebrew 5.1.9" - ;; - tap-info) - exit 1 - ;; - tap-new) - mkdir -p "${BREW_TAP_DIR}/Formula" - ;; - --repository) - echo "$BREW_TAP_DIR" - ;; - list) - exit 1 - ;; - install|reinstall) - ;; - services) - ;; - --prefix) - echo "$BREW_PREFIX" - ;; - *) - echo "unexpected brew command: $*" >&2 - exit 99 - ;; -esac -EOF -chmod +x "${FAKE_BIN}/brew" - -cat >"${BREW_PREFIX}/bin/openshell" <<'EOF' -#!/bin/sh -printf 'openshell %s\n' "$*" >>"$BREW_LOG" -EOF -chmod +x "${BREW_PREFIX}/bin/openshell" - -PATH="${FAKE_BIN}:/usr/bin:/bin:/usr/sbin:/sbin" \ - HOME="${TEST_DIR}/home" \ - BREW_LOG="$BREW_LOG" \ - BREW_TAP_DIR="$BREW_TAP_DIR" \ - BREW_PREFIX="$BREW_PREFIX" \ - sh "${REPO_ROOT}/install-dev.sh" >"$INSTALL_OUTPUT" 2>&1 - -assert_log_contains() { - _needle="$1" - if ! grep -qF "$_needle" "$BREW_LOG"; then - echo "missing log entry: $_needle" >&2 - echo "--- brew log ---" >&2 - cat "$BREW_LOG" >&2 - echo "--- installer output ---" >&2 - cat "$INSTALL_OUTPUT" >&2 - exit 1 - fi -} - -assert_log_contains "brew tap-new --no-git nvidia/openshell-dev" -assert_log_contains "brew install --formula nvidia/openshell-dev/openshell" -assert_log_contains "brew services restart openshell" -assert_log_contains "openshell gateway add http://127.0.0.1:17670 --local --name local" - -if ! grep -qF "class Openshell < Formula" "${BREW_TAP_DIR}/Formula/openshell.rb"; then - echo "formula was not staged into the local tap" >&2 - exit 1 -fi - -if ! grep -qF "entitlements.atomic_write <<~XML" "${BREW_TAP_DIR}/Formula/openshell.rb"; then - echo "formula postinstall was not patched to use atomic_write" >&2 - exit 1 -fi - -if grep -qF "entitlements.write <<~XML" "${BREW_TAP_DIR}/Formula/openshell.rb"; then - echo "formula postinstall still uses non-idempotent write" >&2 - exit 1 -fi - -if grep -Eq 'brew (install|reinstall) --formula .*[.]rb' "$BREW_LOG"; then - echo "installer still passed a formula file path to Homebrew" >&2 - cat "$BREW_LOG" >&2 - exit 1 -fi - -echo "install-dev macOS Homebrew test passed" From afedaf8282cc00d1b158287b76915928887698f8 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Wed, 6 May 2026 18:36:08 -0700 Subject: [PATCH 4/4] fix(installer): stage formula in main tap --- crates/openshell-driver-vm/README.md | 4 ++-- install-dev.sh | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/openshell-driver-vm/README.md b/crates/openshell-driver-vm/README.md index cb11f8db1..78550421b 100644 --- a/crates/openshell-driver-vm/README.md +++ b/crates/openshell-driver-vm/README.md @@ -183,8 +183,8 @@ selected `OPENSHELL_VERSION` release tag. That package includes `openshell-gateway` and `openshell-driver-vm`. On Apple Silicon macOS, `install-dev.sh` stages the generated `openshell.rb` -formula from the selected release in a local `nvidia/openshell-dev` Homebrew -tap. Homebrew installs `openshell`, `openshell-gateway`, and +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 in `post_install`, and owns the `brew services` gateway lifecycle. diff --git a/install-dev.sh b/install-dev.sh index 936b7f8f3..b0cf88f07 100755 --- a/install-dev.sh +++ b/install-dev.sh @@ -16,7 +16,7 @@ GITHUB_URL="https://github.com/${REPO}" RELEASE_TAG="${OPENSHELL_VERSION:-dev}" CHECKSUMS_NAME="openshell-checksums-sha256.txt" LOCAL_GATEWAY_PORT="17670" -HOMEBREW_DEV_TAP="nvidia/openshell-dev" +HOMEBREW_TAP="nvidia/openshell" HOMEBREW_FORMULA_NAME="openshell" info() { @@ -419,15 +419,15 @@ install_macos_homebrew() { chmod 0644 "$_formula_file" patch_homebrew_formula "$_formula_file" - _tap_formula_file="$(homebrew_formula_path "$HOMEBREW_DEV_TAP" "$HOMEBREW_FORMULA_NAME")" - info "staging Homebrew formula in local tap ${HOMEBREW_DEV_TAP}..." + _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_DEV_TAP}/${HOMEBREW_FORMULA_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..." @@ -438,9 +438,9 @@ install_macos_homebrew() { fi info "restarting OpenShell Homebrew service..." - if ! as_target_user brew services restart openshell; then + 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 openshell" + 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