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
This commit is contained in:
Claude Thebot
2026-02-26 15:52:47 -08:00
parent 348b0515ac
commit c402344cb8
4 changed files with 89 additions and 6 deletions

View File

@@ -184,6 +184,17 @@ jobs:
if [ -f .install-logs/frontend.pid ]; then kill "$(cat .install-logs/frontend.pid)" || true; fi 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 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: e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [check] needs: [check]

View File

@@ -76,6 +76,7 @@ Installer support matrix: [`docs/installer-support.md`](./docs/installer-support
### Prerequisites ### 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 Engine
- Docker Compose v2 (`docker compose`) - Docker Compose v2 (`docker compose`)

View File

@@ -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. | | 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. | | 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. | | 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 ## Guard rails

View File

@@ -7,6 +7,7 @@ REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}" STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}"
LOG_DIR="$STATE_DIR/openclaw-mission-control-install" LOG_DIR="$STATE_DIR/openclaw-mission-control-install"
PLATFORM=""
LINUX_DISTRO="" LINUX_DISTRO=""
PKG_MANAGER="" PKG_MANAGER=""
PKG_UPDATED=0 PKG_UPDATED=0
@@ -209,6 +210,9 @@ install_command_hint() {
pacman) pacman)
printf 'sudo pacman -Sy --noconfirm %s' "${packages[*]}" printf 'sudo pacman -Sy --noconfirm %s' "${packages[*]}"
;; ;;
brew)
printf 'brew install %s' "${packages[*]}"
;;
*) *)
printf 'install packages manually: %s' "${packages[*]}" printf 'install packages manually: %s' "${packages[*]}"
;; ;;
@@ -218,10 +222,21 @@ install_command_hint() {
detect_platform() { detect_platform() {
local uname_s local uname_s
uname_s="$(uname -s)" uname_s="$(uname -s)"
if [[ "$uname_s" != "Linux" ]]; then if [[ "$uname_s" == "Darwin" ]]; then
die "Unsupported platform: $uname_s. Linux is required." 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 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 if [[ ! -r /etc/os-release ]]; then
die "Cannot detect Linux distribution (/etc/os-release missing)." die "Cannot detect Linux distribution (/etc/os-release missing)."
fi fi
@@ -268,6 +283,9 @@ install_packages() {
fi fi
as_root apt-get install -y "${packages[@]}" as_root apt-get install -y "${packages[@]}"
;; ;;
brew)
brew install "${packages[@]}"
;;
dnf|yum|zypper|pacman) dnf|yum|zypper|pacman)
die "Automatic package install is not implemented yet for '$PKG_MANAGER'. Run: $(install_command_hint "$PKG_MANAGER" "${packages[@]}")" 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}" input="${input:-n}"
fi fi
case "${input,,}" in case "$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]')" in
y|yes) y|yes)
return 0 return 0
;; ;;
@@ -376,9 +394,33 @@ generate_token() {
fi 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() { ensure_file_from_example() {
local target_file="$1" local target_file="$1"
local example_file="$2" local example_file="$2"
local display_path
if [[ -f "$target_file" ]]; then if [[ -f "$target_file" ]]; then
return return
@@ -389,7 +431,12 @@ ensure_file_from_example() {
fi fi
cp "$example_file" "$target_file" 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() { upsert_env_value() {
@@ -474,8 +521,23 @@ ensure_nodejs() {
die "Cannot continue without Node.js >= 22." die "Cannot continue without Node.js >= 22."
fi 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 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 fi
install_packages ca-certificates curl gnupg install_packages ca-certificates curl gnupg
@@ -505,6 +567,10 @@ ensure_docker() {
return return
fi 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." info "Docker and Docker Compose v2 are required."
if ! confirm "Install Docker tooling now?" "y"; then if ! confirm "Install Docker tooling now?" "y"; then
die "Cannot continue without Docker." die "Cannot continue without Docker."
@@ -620,7 +686,11 @@ main() {
parse_args "$@" parse_args "$@"
detect_platform detect_platform
if [[ "$PLATFORM" == "darwin" ]]; then
info "Platform detected: darwin (macOS)"
else
info "Platform detected: linux ($LINUX_DISTRO)" info "Platform detected: linux ($LINUX_DISTRO)"
fi
if [[ -n "$FORCE_MODE" ]]; then if [[ -n "$FORCE_MODE" ]]; then
deployment_mode="$FORCE_MODE" deployment_mode="$FORCE_MODE"