From 93d78c0ad3a64f619409b9e817347ec3b271ef7a Mon Sep 17 00:00:00 2001 From: Athena Kaminsky Date: Thu, 20 Aug 2026 14:31:37 -0500 Subject: [PATCH] feat(macos): native install path via Homebrew install-macos.sh mirrors install.sh's split: every portable step - git pull, venv, pip with the right overlay, npm build - stays in bin/sync.py, shared with Linux and Windows. The script only does what sync.py cannot do for itself on a bare Mac, which is install the Homebrew packages needed before a Python exists to run sync.py with. Two stages had to learn about darwin. ensure_exec_bits() keyed off `os.name == "nt"`, which is false on macOS, so it ran the Linux path; and requirements() had no darwin branch. linux_stage() now no-ops there, which is what makes skipping the Ollama fetch correct rather than an omission: bin/fetch-ollama.sh only ships a Linux x86-64 binary, and _ollama_bin() in synapse/ollama_manager.py already prefers the bundled copy and falls back to whatever `ollama` is on PATH. On macOS that is the brewed one, with Metal acceleration and no flags needed. The XFCE desktop branding is Linux-only and was already gated off macOS the same way, so there is nothing to install for it here. install-macos.sh joins the shell-parse list in bin/check.sh. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 13 ++++++++++++- README.md | 39 +++++++++++++++++++++++++++++++++++---- bin/check.sh | 2 +- bin/sync.py | 15 ++++++++++++--- install-macos.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 9 deletions(-) create mode 100755 install-macos.sh diff --git a/CLAUDE.md b/CLAUDE.md index 824d045..fcbed3f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,17 @@ desktop shortcut runs and still works directly. ``` This activates the `Promethean` venv and starts the Synapse backend on port 8000 (which also serves the built UI). It additionally starts a **Vite dev server** for frontend hot-reload — a Linux-dev convenience, unlike the single-process Windows/production path where the backend serves `dist/` alone. It does **not** start Ollama. +**macOS (community-supported):** +```bash +./install-macos.sh # one-time: Homebrew packages + venv + web build, via bin/sync.py +./launch_nexus.sh # same script as Linux - it's plain bash, no Linux-only calls +``` +No bundled Ollama binary (Linux x86-64 only) and no XFCE desktop branding — both +already no-op on macOS via `bin/sync.py`'s `linux_stage()`. Ollama is instead the +Homebrew-installed native binary, picked up automatically because +`OllamaManager` falls back to `ollama` on PATH when the bundled binary is +absent; that gets full Metal GPU acceleration with no extra config. + **Individual services via CLI:** ```bash # From nexus-core/ with Promethean venv active: @@ -92,7 +103,7 @@ cd interface/web && npm run build ## Architecture ### Python venv -All Python code runs inside `Promethean/` (a local venv). Always activate it before running backend commands: `source Promethean/bin/activate`. Dependencies are layered: `requirements-base.txt` holds the GPU-agnostic core (nothing in it needs a GPU or imports torch), and a thin overlay per platform sets the right PyTorch package index — `requirements-amd.txt` (ROCm), `requirements-nvidia.txt` (CUDA, generated by `bin/gen-nvidia-reqs.py`), or `requirements-windows.txt` (CPU-only, standalone). `bin/sync.py` (`requirements()`) selects NVIDIA, AMD, or CPU/Windows requirements from the host and installs that alone by default — fast, no multi-GB downloads. +All Python code runs inside `Promethean/` (a local venv). Always activate it before running backend commands: `source Promethean/bin/activate`. Dependencies are layered: `requirements-base.txt` holds the GPU-agnostic core (nothing in it needs a GPU or imports torch), and a thin overlay per platform sets the right PyTorch package index — `requirements-amd.txt` (ROCm), `requirements-nvidia.txt` (CUDA, generated by `bin/gen-nvidia-reqs.py`), or `requirements-windows.txt` (CPU-only, standalone). macOS uses `requirements-base.txt` without an overlay because Ollama handles inference outside the venv. `bin/sync.py` (`requirements()`) selects the appropriate requirements for the host and installs that alone by default — fast, no multi-GB downloads. `requirements-ml.txt` is a separate, **opt-in** overlay for local ML inference (transformers/accelerate/bitsandbytes + torch/torchaudio/torchvision) — nothing in `synapse/` imports any of it; Ollama does all inference over HTTP. Only pull it in for local model work outside Ollama: `pip install -r requirements-amd.txt -r requirements-ml.txt` (or `-nvidia`, or alone for CPU-only torch). Not installed by `bin/sync.py`/the installers. diff --git a/README.md b/README.md index f2d22a4..b03e26c 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,8 @@ ncp web Python deps are layered: `requirements-base.txt` (GPU-agnostic core) plus one GPU overlay — `requirements-amd.txt` (ROCm) or `requirements-nvidia.txt` (CUDA). `requirements-windows.txt` is the standalone CPU-only runtime (no base overlay). -`bin/sync.py` picks the right one for the host. +macOS uses `requirements-base.txt` directly, no overlay — see the macOS section +below. `bin/sync.py` picks the right one for the host. `./install.sh` is also the update path — re-run it any time to pull the latest and rebuild. `--check` dry-runs it; `--no-desktop` skips the XFCE panel/theme @@ -172,10 +173,38 @@ The app opens at `:8000`; click **Start AI** to launch Ollama. The installer uses `requirements-windows.txt` (CPU-only, pure-Python — no ML stack, since Ollama does all inference over HTTP). +### macOS + +Community-supported — no bundled Ollama binary or XFCE desktop branding (that +stage is Linux/XFCE-only and already skips itself here), but the +backend/frontend/Ollama stack itself runs natively, no VM or container needed. + +```bash +# 1. Install Homebrew first if you don't have it: https://brew.sh + +# 2. Build everything: Homebrew packages (Python, Node, git, Ollama), venv, +# web UI, memory DB. +./install-macos.sh + +# 3. Launch (memory :8001, backend :8000 — backend also serves the built UI) +./launch_nexus.sh +``` + +Ollama here is the Homebrew-installed native binary, not the Linux-only bundled +one — `OllamaManager` already falls back to `ollama` on PATH when +`ollama/bin/ollama` doesn't exist, so **Start AI** in the sidebar (or `ollama +serve` in a terminal) uses it with full Metal GPU acceleration automatically, +no configuration needed. + +`./install-macos.sh` is also the update path, same idea as Linux — re-run it +any time to pull the latest and rebuild; `--check` dry-runs it. It's a thin +wrapper over `bin/sync.py restore`, the same code Linux and `ncp restore` +(any platform) run. + ### Individual services ```bash -# Linux +# Linux / macOS source Promethean/bin/activate uvicorn synapse.main:sio_app --host 127.0.0.1 --port 8000 --reload # backend (serves the UI too) @@ -199,7 +228,7 @@ Nexus's dependencies out of the system Python. To add a package, activate it and `pip install` as usual: ```bash -# Linux +# Linux / macOS source Promethean/bin/activate pip install ``` @@ -274,7 +303,9 @@ are the exception (YAML files in `data/playbooks/`). All paths are defined in - **Filesystem paths** — `synapse/nexus_config.py` - **Frontend API base URL** — `interface/web/src/config.js` - **Python deps** — `requirements-base.txt` + amd/nvidia GPU overlay; - `requirements-windows.txt` = standalone CPU runtime + `requirements-windows.txt` = standalone CPU runtime; macOS uses + `requirements-base.txt` with no overlay (Ollama, not this venv, does + inference — natively, with Metal) ## Issues and feature requests diff --git a/bin/check.sh b/bin/check.sh index a8dbbe4..02b9b21 100644 --- a/bin/check.sh +++ b/bin/check.sh @@ -40,7 +40,7 @@ else fi echo "== shell parse ==" -for f in scripts/install-termux.sh launch_nexus.sh management/nexus-cli.sh; do +for f in scripts/install-termux.sh install-macos.sh launch_nexus.sh management/nexus-cli.sh; do [ -f "$f" ] && { bash -n "$f" || fail=1; } done diff --git a/bin/sync.py b/bin/sync.py index ab56a4d..9857939 100644 --- a/bin/sync.py +++ b/bin/sync.py @@ -66,9 +66,11 @@ def ensure_exec_bits() -> None: def linux_stage(script: str, *args) -> None: - """Run one of the Linux-only bash stages. A no-op on Windows, where apt, - xfconf, plank and the rest have nothing to act on.""" - if os.name == "nt": + """Run one of the Linux-only bash stages. A no-op on Windows and macOS, + where apt, xfconf, plank and the rest have nothing to act on. os.name is + 'posix' on both Linux and macOS, so the Windows-only os.name check alone + doesn't exclude macOS - needs the explicit darwin check too.""" + if os.name == "nt" or sys.platform == "darwin": return path = ROOT / "bin" / script bash = shutil.which("bash") @@ -109,6 +111,13 @@ def requirements() -> str: """Pick the PyTorch overlay for this host.""" if os.name == "nt": return "requirements-windows.txt" # CPU / pure-Python, right for native Windows + if sys.platform == "darwin": + # No ROCm/CUDA overlay applies here, and none is needed: Ollama does + # all inference over HTTP (see requirements-ml.txt), and on macOS + # that's a natively-installed, Metal-accelerated Ollama binary + # (OllamaManager falls back to it on PATH - see synapse/ollama_manager.py), + # entirely outside this venv. + return "requirements-base.txt" if shutil.which("nvidia-smi"): return "requirements-nvidia.txt" lspci = shutil.which("lspci") diff --git a/install-macos.sh b/install-macos.sh new file mode 100755 index 0000000..45acc1e --- /dev/null +++ b/install-macos.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# NexusOS installer, macOS. One painless command: +# +# git clone nexus-core && cd nexus-core && ./install-macos.sh +# +# Mirrors install.sh's philosophy: every portable step - git pull, venv, pip +# with the right overlay, npm build - lives in bin/sync.py, shared with Linux +# and Windows. This script only does what sync.py can't do for itself on a +# bare Mac: install the Homebrew packages needed before Python even exists to +# run sync.py with. Re-run any time to update; --check dry-runs it. +# +# Ollama itself is *not* fetched here - bin/fetch-ollama.sh only ships a Linux +# x86-64 binary, and linux_stage() in bin/sync.py already no-ops on macOS, so +# that stage is skipped entirely. The Homebrew `ollama` installed below is +# picked up automatically instead: synapse/ollama_manager.py prefers the +# bundled Linux binary and falls back to whatever `ollama` it finds on PATH, +# which on macOS is this one - with full Metal GPU acceleration, no flags +# needed. The XFCE desktop branding (theme/panel/splash) is Linux-only and +# already gated off macOS the same way; nothing to install for it here. +set -euo pipefail +cd "$(dirname "$0")" + +if ! command -v brew >/dev/null; then + echo "Homebrew is required (it installs Python/Node/git/Ollama)." >&2 + echo "Install it, then re-run this script: https://brew.sh" >&2 + exit 1 +fi + +echo "Installing/checking system packages (python@3.12, node, git, ollama)..." +brew install python@3.12 node git ollama + +py="$(brew --prefix python@3.12)/bin/python3.12" +if [ ! -x "$py" ]; then + echo "python3.12 not found at $py after brew install - check 'brew doctor'." >&2 + exit 1 +fi + +# Prefer the venv interpreter once it exists, same as install.sh; the brewed +# interpreter above is only the bootstrap case on a fresh clone. sync.py is +# stdlib-only either way. +[ -x "Promethean/bin/python" ] && py="Promethean/bin/python" + +exec "$py" bin/sync.py restore "$@"