From c402344cb81ab395f378310f8ed2aeca0de77ca0 Mon Sep 17 00:00:00 2001 From: Claude Thebot Date: Thu, 26 Feb 2026 15:52:47 -0800 Subject: [PATCH 1/2] feat: add macOS support to installer and docs - install.sh: detect Darwin, use Homebrew for packages/Node, Docker Desktop hint - install.sh: portable realpath and bash 3-compatible confirm() for macOS - docs/installer-support.md: add macOS (Darwin) / Homebrew to support matrix - README.md: document supported platforms (Linux and macOS), Docker Desktop/Homebrew - ci.yml: add installer-macos job (macos-latest, bash -n install.sh) Made-with: Cursor --- .github/workflows/ci.yml | 11 ++++++ README.md | 1 + docs/installer-support.md | 1 + install.sh | 82 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 773db02a..aabd9a96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,6 +184,17 @@ jobs: if [ -f .install-logs/frontend.pid ]; then kill "$(cat .install-logs/frontend.pid)" || true; fi docker compose -f compose.yml --env-file .env down -v --remove-orphans || true + installer-macos: + runs-on: macos-latest + needs: [check] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate installer shell syntax (macOS) + run: bash -n install.sh + e2e: runs-on: ubuntu-latest needs: [check] diff --git a/README.md b/README.md index 159fa500..8f17b463 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Installer support matrix: [`docs/installer-support.md`](./docs/installer-support ### Prerequisites +- **Supported platforms**: Linux and macOS. On macOS, Docker mode requires [Docker Desktop](https://www.docker.com/products/docker-desktop/); local mode requires [Homebrew](https://brew.sh) and Node.js 22+. - Docker Engine - Docker Compose v2 (`docker compose`) diff --git a/docs/installer-support.md b/docs/installer-support.md index e870914d..6f06600a 100644 --- a/docs/installer-support.md +++ b/docs/installer-support.md @@ -17,6 +17,7 @@ This document defines current support status for `./install.sh`. | openSUSE | `zypper` | **Scaffolded** | Detection + actionable commands present; auto-install path is TODO. | | Arch Linux | `pacman` | **Scaffolded** | Detection + actionable commands present; auto-install path is TODO. | | Other Linux distros | unknown | **Unsupported** | Installer exits with package-manager guidance requirement. | +| macOS (Darwin) | Homebrew | **Stable** | Docker mode requires Docker Desktop. Local mode uses Homebrew for curl, git, make, openssl, Node.js. | ## Guard rails diff --git a/install.sh b/install.sh index 25f93b7d..2369055c 100755 --- a/install.sh +++ b/install.sh @@ -7,6 +7,7 @@ REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}" LOG_DIR="$STATE_DIR/openclaw-mission-control-install" +PLATFORM="" LINUX_DISTRO="" PKG_MANAGER="" PKG_UPDATED=0 @@ -209,6 +210,9 @@ install_command_hint() { pacman) printf 'sudo pacman -Sy --noconfirm %s' "${packages[*]}" ;; + brew) + printf 'brew install %s' "${packages[*]}" + ;; *) printf 'install packages manually: %s' "${packages[*]}" ;; @@ -218,10 +222,21 @@ install_command_hint() { detect_platform() { local uname_s uname_s="$(uname -s)" - if [[ "$uname_s" != "Linux" ]]; then - die "Unsupported platform: $uname_s. Linux is required." + if [[ "$uname_s" == "Darwin" ]]; then + PLATFORM="darwin" + PKG_MANAGER="brew" + if ! command_exists brew; then + die "Homebrew is required on macOS. Install from https://brew.sh, then re-run this script." + fi + info "Detected platform: darwin (macOS), package manager: Homebrew" + return fi + if [[ "$uname_s" != "Linux" ]]; then + die "Unsupported platform: $uname_s. Linux and macOS (Darwin) are supported." + fi + + PLATFORM="linux" if [[ ! -r /etc/os-release ]]; then die "Cannot detect Linux distribution (/etc/os-release missing)." fi @@ -268,6 +283,9 @@ install_packages() { fi as_root apt-get install -y "${packages[@]}" ;; + brew) + brew install "${packages[@]}" + ;; dnf|yum|zypper|pacman) die "Automatic package install is not implemented yet for '$PKG_MANAGER'. Run: $(install_command_hint "$PKG_MANAGER" "${packages[@]}")" ;; @@ -351,7 +369,7 @@ confirm() { input="${input:-n}" fi - case "${input,,}" in + case "$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]')" in y|yes) return 0 ;; @@ -376,9 +394,33 @@ generate_token() { fi } +# Portable relative path: print path of $1 relative to $2 (both absolute). +relative_to() { + local target="$1" + local base="$2" + local rel + if [[ -z "$base" || -z "$target" ]]; then + printf '%s' "$target" + return + fi + base="${base%/}" + target="${target%/}" + if [[ "$target" == "$base" ]]; then + printf '' + return + fi + rel="${target#$base/}" + if [[ "$rel" != "$target" ]]; then + printf '%s' "$rel" + else + printf '%s' "$target" + fi +} + ensure_file_from_example() { local target_file="$1" local example_file="$2" + local display_path if [[ -f "$target_file" ]]; then return @@ -389,7 +431,12 @@ ensure_file_from_example() { fi cp "$example_file" "$target_file" - info "Created $(realpath --relative-to="$REPO_ROOT" "$target_file" 2>/dev/null || printf '%s' "$target_file")" + if command_exists realpath && realpath --relative-to="$REPO_ROOT" "$target_file" >/dev/null 2>&1; then + display_path="$(realpath --relative-to="$REPO_ROOT" "$target_file" 2>/dev/null)" + else + display_path="$(relative_to "$(cd -- "$(dirname -- "$target_file")" && pwd -P)/$(basename "$target_file")" "$REPO_ROOT")" + fi + info "Created $display_path" } upsert_env_value() { @@ -474,8 +521,23 @@ ensure_nodejs() { die "Cannot continue without Node.js >= 22." fi + if [[ "$PLATFORM" == "darwin" ]]; then + brew install node + if ! command_exists node || ! command_exists npm; then + die 'Node.js/npm installation failed. Ensure Homebrew bin is in PATH (e.g. eval "$(brew shellenv)").' + fi + hash -r || true + node_version="$(node -v || true)" + node_major="${node_version#v}" + node_major="${node_major%%.*}" + if [[ ! "$node_major" =~ ^[0-9]+$ ]] || ((node_major < 22)); then + die "Detected Node.js $node_version. Node.js >= 22 is required. Install with: brew install node@22 and link or adjust PATH." + fi + return + fi + if [[ "$PKG_MANAGER" != "apt" ]]; then - die "Node.js auto-install is currently implemented for apt-based distros only. Install Node.js >= 22 manually, then rerun installer. Suggested command: $(install_command_hint "$PKG_MANAGER" nodejs npm)" + die "Node.js auto-install is currently implemented for apt-based distros and macOS only. Install Node.js >= 22 manually, then rerun installer. Suggested command: $(install_command_hint "$PKG_MANAGER" nodejs npm)" fi install_packages ca-certificates curl gnupg @@ -505,6 +567,10 @@ ensure_docker() { return fi + if [[ "$PLATFORM" == "darwin" ]]; then + die "Docker and Docker Compose v2 are required on macOS. Install Docker Desktop from https://www.docker.com/products/docker-desktop/, start it, then re-run this script." + fi + info "Docker and Docker Compose v2 are required." if ! confirm "Install Docker tooling now?" "y"; then die "Cannot continue without Docker." @@ -620,7 +686,11 @@ main() { parse_args "$@" detect_platform - info "Platform detected: linux ($LINUX_DISTRO)" + if [[ "$PLATFORM" == "darwin" ]]; then + info "Platform detected: darwin (macOS)" + else + info "Platform detected: linux ($LINUX_DISTRO)" + fi if [[ -n "$FORCE_MODE" ]]; then deployment_mode="$FORCE_MODE" From ee1bec0525c4a5b22731a4de1eb5c8fcd9db3b3c Mon Sep 17 00:00:00 2001 From: Claude Thebot Date: Sat, 28 Feb 2026 06:39:51 -0800 Subject: [PATCH 2/2] fix(install): upgrade existing Node on macOS and use node@22 consistently - Use 'brew upgrade node@22 || brew install node@22' so existing older Node is upgraded - Add node@22 bin to PATH after install so node/npm are found - Align error message with node@22 (was suggesting node@22 while install used node) Made-with: Cursor --- install.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 5565a974..5924de60 100755 --- a/install.sh +++ b/install.sh @@ -522,7 +522,10 @@ ensure_nodejs() { fi if [[ "$PLATFORM" == "darwin" ]]; then - brew install node + brew upgrade node@22 2>/dev/null || brew install node@22 + if [[ -d "$(brew --prefix node@22 2>/dev/null)/bin" ]]; then + export PATH="$(brew --prefix node@22)/bin:$PATH" + fi if ! command_exists node || ! command_exists npm; then die 'Node.js/npm installation failed. Ensure Homebrew bin is in PATH (e.g. eval "$(brew shellenv)").' fi @@ -531,7 +534,7 @@ ensure_nodejs() { node_major="${node_version#v}" node_major="${node_major%%.*}" if [[ ! "$node_major" =~ ^[0-9]+$ ]] || ((node_major < 22)); then - die "Detected Node.js $node_version. Node.js >= 22 is required. Install with: brew install node@22 and link or adjust PATH." + die "Detected Node.js $node_version. Node.js >= 22 is required. Install with: brew install node@22 and ensure $(brew --prefix node@22 2>/dev/null || echo '/opt/homebrew/opt/node@22')/bin is in PATH." fi return fi