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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5f67d19e80
commit
93d78c0ad3
@@ -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.
|
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:**
|
**Individual services via CLI:**
|
||||||
```bash
|
```bash
|
||||||
# From nexus-core/ with Promethean venv active:
|
# From nexus-core/ with Promethean venv active:
|
||||||
@@ -92,7 +103,7 @@ cd interface/web && npm run build
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Python venv
|
### 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.
|
`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.
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ ncp web
|
|||||||
Python deps are layered: `requirements-base.txt` (GPU-agnostic core) plus one
|
Python deps are layered: `requirements-base.txt` (GPU-agnostic core) plus one
|
||||||
GPU overlay — `requirements-amd.txt` (ROCm) or `requirements-nvidia.txt` (CUDA).
|
GPU overlay — `requirements-amd.txt` (ROCm) or `requirements-nvidia.txt` (CUDA).
|
||||||
`requirements-windows.txt` is the standalone CPU-only runtime (no base overlay).
|
`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
|
`./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
|
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
|
uses `requirements-windows.txt` (CPU-only, pure-Python — no ML stack, since Ollama
|
||||||
does all inference over HTTP).
|
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
|
### Individual services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux
|
# Linux / macOS
|
||||||
source Promethean/bin/activate
|
source Promethean/bin/activate
|
||||||
|
|
||||||
uvicorn synapse.main:sio_app --host 127.0.0.1 --port 8000 --reload # backend (serves the UI too)
|
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:
|
and `pip install` as usual:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux
|
# Linux / macOS
|
||||||
source Promethean/bin/activate
|
source Promethean/bin/activate
|
||||||
pip install <package>
|
pip install <package>
|
||||||
```
|
```
|
||||||
@@ -274,7 +303,9 @@ are the exception (YAML files in `data/playbooks/`). All paths are defined in
|
|||||||
- **Filesystem paths** — `synapse/nexus_config.py`
|
- **Filesystem paths** — `synapse/nexus_config.py`
|
||||||
- **Frontend API base URL** — `interface/web/src/config.js`
|
- **Frontend API base URL** — `interface/web/src/config.js`
|
||||||
- **Python deps** — `requirements-base.txt` + amd/nvidia GPU overlay;
|
- **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
|
## Issues and feature requests
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "== shell parse =="
|
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; }
|
[ -f "$f" ] && { bash -n "$f" || fail=1; }
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -66,9 +66,11 @@ def ensure_exec_bits() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def linux_stage(script: str, *args) -> None:
|
def linux_stage(script: str, *args) -> None:
|
||||||
"""Run one of the Linux-only bash stages. A no-op on Windows, where apt,
|
"""Run one of the Linux-only bash stages. A no-op on Windows and macOS,
|
||||||
xfconf, plank and the rest have nothing to act on."""
|
where apt, xfconf, plank and the rest have nothing to act on. os.name is
|
||||||
if os.name == "nt":
|
'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
|
return
|
||||||
path = ROOT / "bin" / script
|
path = ROOT / "bin" / script
|
||||||
bash = shutil.which("bash")
|
bash = shutil.which("bash")
|
||||||
@@ -109,6 +111,13 @@ def requirements() -> str:
|
|||||||
"""Pick the PyTorch overlay for this host."""
|
"""Pick the PyTorch overlay for this host."""
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
return "requirements-windows.txt" # CPU / pure-Python, right for native Windows
|
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"):
|
if shutil.which("nvidia-smi"):
|
||||||
return "requirements-nvidia.txt"
|
return "requirements-nvidia.txt"
|
||||||
lspci = shutil.which("lspci")
|
lspci = shutil.which("lspci")
|
||||||
|
|||||||
Executable
+43
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# NexusOS installer, macOS. One painless command:
|
||||||
|
#
|
||||||
|
# git clone <repo> 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 "$@"
|
||||||
Reference in New Issue
Block a user