From 15f1ddf95e2449c3432932f057b7d229e56a90ba Mon Sep 17 00:00:00 2001 From: shijianjian Date: Fri, 20 Mar 2026 23:45:06 +0300 Subject: [PATCH] add macos support --- dashboard/package-lock.json | 20 -- dashboard/scripts/generate-proto.sh | 4 +- scripts/install-macos.sh | 474 ++++++++++++++++++++++++++++ 3 files changed, 476 insertions(+), 22 deletions(-) create mode 100755 scripts/install-macos.sh diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index b5e9d571..74a08e26 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -137,7 +137,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -487,7 +486,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -528,7 +526,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -550,7 +547,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -1950,7 +1946,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2209,7 +2204,6 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -2244,7 +2238,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2256,7 +2249,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -2411,7 +2403,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2577,7 +2568,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3026,7 +3016,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3613,7 +3602,6 @@ "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.7.6", @@ -3816,7 +3804,6 @@ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -4104,7 +4091,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4184,7 +4170,6 @@ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "hasInstallScript": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -4300,7 +4285,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4313,7 +4297,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4400,7 +4383,6 @@ "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -4834,7 +4816,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -4936,7 +4917,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", diff --git a/dashboard/scripts/generate-proto.sh b/dashboard/scripts/generate-proto.sh index 69c81a7c..dd1cd885 100755 --- a/dashboard/scripts/generate-proto.sh +++ b/dashboard/scripts/generate-proto.sh @@ -17,5 +17,5 @@ npx pbjs -t static-module -w es6 \ npx pbts -o src/proto/messages.pb.d.ts src/proto/messages.pb.js # Fix protobufjs 7.x bug that adds incorrect 'error' parameter to decode functions -sed -i 's/function decode(reader, length, error)/function decode(reader, length)/g' src/proto/messages.pb.js -sed -i 's/if (tag === error)/if (false)/g' src/proto/messages.pb.js +sed -i '' 's/function decode(reader, length, error)/function decode(reader, length)/g' src/proto/messages.pb.js +sed -i '' 's/if (tag === error)/if (false)/g' src/proto/messages.pb.js diff --git a/scripts/install-macos.sh b/scripts/install-macos.sh new file mode 100755 index 00000000..26335a78 --- /dev/null +++ b/scripts/install-macos.sh @@ -0,0 +1,474 @@ +#!/bin/bash +# Bubbaloop macOS Installer +# Usage: ./scripts/install-macos.sh [--skip-build] +# +# This script installs: +# - Zenoh router (zenohd) +# - Zenoh WebSocket bridge (zenoh-bridge-remote-api) +# - Bubbaloop daemon + CLI (built from source) +# - Bubbaloop dashboard server (built from source, optional) +# - launchd user agents to manage the above +# +# No prebuilt macOS binaries exist yet — this script builds from source +# using a macOS-compatible release profile (no linker-plugin-lto). + +set -euo pipefail + +REPO="kornia/bubbaloop" +ZENOH_VERSION="1.7.1" +INSTALL_DIR="$HOME/.bubbaloop" +BIN_DIR="$INSTALL_DIR/bin" +LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents" + +PLIST_ZENOH="$LAUNCH_AGENTS_DIR/com.bubbaloop.zenohd.plist" +PLIST_BRIDGE="$LAUNCH_AGENTS_DIR/com.bubbaloop.zenoh-bridge-remote-api.plist" +PLIST_DAEMON="$LAUNCH_AGENTS_DIR/com.bubbaloop.daemon.plist" +PLIST_DASH="$LAUNCH_AGENTS_DIR/com.bubbaloop.dashboard.plist" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } +step() { echo -e "${BLUE}[STEP]${NC} $1"; } + +detect_os() { + local os + os=$(uname -s) + case "$os" in + Darwin) echo "darwin" ;; + *) error "This installer is for macOS only. For Linux, use scripts/install.sh." ;; + esac +} + +detect_arch() { + local arch + arch=$(uname -m) + case "$arch" in + x86_64) echo "x86_64-apple-darwin" ;; + arm64) echo "aarch64-apple-darwin" ;; + aarch64) echo "aarch64-apple-darwin" ;; + *) error "Unsupported architecture: $arch" ;; + esac +} + +check_deps() { + local missing=() + + if ! command -v curl >/dev/null 2>&1; then + missing+=("curl") + fi + if ! command -v unzip >/dev/null 2>&1; then + missing+=("unzip") + fi + if ! command -v cargo >/dev/null 2>&1; then + missing+=("cargo (install via https://rustup.rs)") + fi + + if [ ${#missing[@]} -gt 0 ]; then + error "Missing required dependencies: ${missing[*]}" + fi +} + +download() { + local url="$1" + local dest="$2" + info "Downloading $(basename "$dest")..." + curl -sSL "$url" -o "$dest" || error "Failed to download: $url" +} + +# ── Zenoh ──────────────────────────────────────────────────────────── + +install_zenoh() { + local arch="$1" + local zenoh_dir="$INSTALL_DIR/zenoh" + + if [ -f "$BIN_DIR/zenohd" ]; then + local current_version + current_version=$("$BIN_DIR/zenohd" --version 2>/dev/null | head -1 | awk '{print $2}' || echo "unknown") + info "Zenoh $current_version already installed, upgrading to $ZENOH_VERSION..." + fi + + step "Installing Zenoh $ZENOH_VERSION..." + mkdir -p "$zenoh_dir" + + local zip="/tmp/zenoh.zip" + local url="https://github.com/eclipse-zenoh/zenoh/releases/download/${ZENOH_VERSION}/zenoh-${ZENOH_VERSION}-${arch}-standalone.zip" + + info "Downloading Zenoh..." + curl -sSL "$url" -o "$zip" || error "Failed to download Zenoh" + + info "Extracting Zenoh..." + unzip -o -q "$zip" -d "$zenoh_dir" + rm -f "$zip" + + cp "$zenoh_dir/zenohd" "$BIN_DIR/" + chmod +x "$BIN_DIR/zenohd" + + info "Zenoh installed: $("$BIN_DIR/zenohd" --version 2>/dev/null | head -1 || echo "$ZENOH_VERSION")" +} + +install_bridge() { + local arch="$1" + local bridge_dir="$INSTALL_DIR/zenoh-bridge" + + step "Installing zenoh-bridge-remote-api $ZENOH_VERSION..." + mkdir -p "$bridge_dir" + + local zip="/tmp/zenoh-bridge.zip" + local url="https://github.com/eclipse-zenoh/zenoh-ts/releases/download/${ZENOH_VERSION}/zenoh-ts-${ZENOH_VERSION}-${arch}-standalone.zip" + + info "Downloading zenoh-bridge-remote-api..." + curl -sSL "$url" -o "$zip" || error "Failed to download zenoh-bridge-remote-api" + + info "Extracting zenoh-bridge-remote-api..." + unzip -o -q "$zip" -d "$bridge_dir" + rm -f "$zip" + + cp "$bridge_dir/zenoh-bridge-remote-api" "$BIN_DIR/" + chmod +x "$BIN_DIR/zenoh-bridge-remote-api" + + info "zenoh-bridge-remote-api installed" +} + +# ── Build from source ──────────────────────────────────────────────── + +find_repo_root() { + local dir + dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$dir/Cargo.toml" ]; then + echo "$dir" + else + error "Cannot find repo root (expected Cargo.toml in $dir). Run this script from the bubbaloop repo." + fi +} + +build_bubbaloop() { + local repo_root + repo_root=$(find_repo_root) + + step "Building Bubbaloop from source (macOS-compatible profile)..." + info "Repo root: $repo_root" + + # The workspace Cargo.toml uses [profile.release] with lto=true and + # opt-level="z" which triggers linker-plugin-lto — unsupported by the + # default macOS linker. Override with CARGO_PROFILE_RELEASE_LTO=false. + ( + cd "$repo_root" + CARGO_PROFILE_RELEASE_LTO=false \ + cargo build --release -p bubbaloop 2>&1 + ) || error "cargo build failed. Check the output above." + + cp "$repo_root/target/release/bubbaloop" "$BIN_DIR/bubbaloop" + chmod +x "$BIN_DIR/bubbaloop" + + if [ -f "$repo_root/target/release/bubbaloop-dash" ]; then + cp "$repo_root/target/release/bubbaloop-dash" "$BIN_DIR/bubbaloop-dash" + chmod +x "$BIN_DIR/bubbaloop-dash" + info "bubbaloop-dash installed" + else + warn "bubbaloop-dash not found in build output (dashboard feature may be missing), skipping" + fi + + local version + version=$("$BIN_DIR/bubbaloop" --version 2>/dev/null || echo "dev") + echo "$version" > "$INSTALL_DIR/version" + info "Bubbaloop built and installed ($version)" +} + +# ── Configuration ──────────────────────────────────────────────────── + +setup_zenoh_config() { + step "Setting up Zenoh configuration..." + + mkdir -p "$INSTALL_DIR/zenoh" + mkdir -p "$INSTALL_DIR/configs" + mkdir -p "$INSTALL_DIR/nodes" + + cat > "$INSTALL_DIR/zenoh/zenohd.json5" << 'CONFIG' +{ + mode: "router", + listen: { + endpoints: ["tcp/127.0.0.1:7447"] + }, + scouting: { + multicast: { + enabled: false + }, + gossip: { + enabled: false + } + } +} +CONFIG + + cp "$INSTALL_DIR/zenoh/zenohd.json5" "$INSTALL_DIR/zenoh.json5" + info "Zenoh config created at $INSTALL_DIR/zenoh/zenohd.json5" +} + +setup_marketplace() { + step "Setting up marketplace..." + + cat > "$INSTALL_DIR/sources.json" << 'EOF' +{ + "sources": [ + { + "name": "Official Nodes", + "path": "kornia/bubbaloop-nodes-official", + "type": "builtin", + "enabled": true + } + ] +} +EOF + + info "Marketplace configured with official nodes registry" +} + +# ── launchd services ───────────────────────────────────────────────── + +stop_services() { + step "Stopping existing launchd agents (if any)..." + + for plist in "$PLIST_DASH" "$PLIST_DAEMON" "$PLIST_BRIDGE" "$PLIST_ZENOH"; do + if [ -f "$plist" ]; then + launchctl unload -w "$plist" 2>/dev/null || true + fi + done +} + +setup_launchd() { + step "Configuring launchd agents..." + mkdir -p "$LAUNCH_AGENTS_DIR" + mkdir -p "$INSTALL_DIR/logs" + + cat > "$PLIST_ZENOH" < + + + + Label + com.bubbaloop.zenohd + ProgramArguments + + $BIN_DIR/zenohd + -c + $INSTALL_DIR/zenoh/zenohd.json5 + + RunAtLoad + + KeepAlive + + StandardOutPath + $INSTALL_DIR/logs/zenohd.log + StandardErrorPath + $INSTALL_DIR/logs/zenohd.err.log + + +EOF + + cat > "$PLIST_BRIDGE" < + + + + Label + com.bubbaloop.zenoh-bridge-remote-api + ProgramArguments + + $BIN_DIR/zenoh-bridge-remote-api + --ws-port + 10001 + -e + tcp/127.0.0.1:7447 + + RunAtLoad + + KeepAlive + + StandardOutPath + $INSTALL_DIR/logs/zenoh-bridge.log + StandardErrorPath + $INSTALL_DIR/logs/zenoh-bridge.err.log + + +EOF + + cat > "$PLIST_DAEMON" < + + + + Label + com.bubbaloop.daemon + ProgramArguments + + $BIN_DIR/bubbaloop + daemon + + EnvironmentVariables + + RUST_LOG + info + + RunAtLoad + + KeepAlive + + StandardOutPath + $INSTALL_DIR/logs/bubbaloop-daemon.log + StandardErrorPath + $INSTALL_DIR/logs/bubbaloop-daemon.err.log + + +EOF + + cat > "$PLIST_DASH" < + + + + Label + com.bubbaloop.dashboard + ProgramArguments + + $BIN_DIR/bubbaloop-dash + --port + 8080 + + RunAtLoad + + KeepAlive + + StandardOutPath + $INSTALL_DIR/logs/bubbaloop-dashboard.log + StandardErrorPath + $INSTALL_DIR/logs/bubbaloop-dashboard.err.log + + +EOF + + info "launchd agents written to $LAUNCH_AGENTS_DIR" +} + +start_services() { + step "Loading launchd agents..." + + launchctl load -w "$PLIST_ZENOH" 2>/dev/null || true + sleep 1 + launchctl load -w "$PLIST_BRIDGE" 2>/dev/null || true + launchctl load -w "$PLIST_DAEMON" 2>/dev/null || true + + if [ -x "$BIN_DIR/bubbaloop-dash" ]; then + launchctl load -w "$PLIST_DASH" 2>/dev/null || true + fi + + info "Services started. Logs: $INSTALL_DIR/logs/" +} + +setup_path() { + local shell_rc="" + if [ -f "$HOME/.zshrc" ]; then + shell_rc="$HOME/.zshrc" + elif [ -f "$HOME/.bashrc" ]; then + shell_rc="$HOME/.bashrc" + else + shell_rc="$HOME/.profile" + fi + + if ! grep -q "$BIN_DIR" "$shell_rc" 2>/dev/null; then + echo "" >> "$shell_rc" + echo "# Bubbaloop" >> "$shell_rc" + echo "export PATH=\"$BIN_DIR:\$PATH\"" >> "$shell_rc" + info "Added $BIN_DIR to PATH in $shell_rc" + fi + + echo "$shell_rc" +} + +# ── Main ───────────────────────────────────────────────────────────── + +usage() { + echo "Usage: $0 [--skip-build]" + echo + echo "Options:" + echo " --skip-build Skip cargo build (use if you already built manually)" + exit 0 +} + +main() { + local skip_build=false + for arg in "$@"; do + case "$arg" in + --skip-build) skip_build=true ;; + --help|-h) usage ;; + esac + done + + echo + echo -e "${GREEN}╔══════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ Bubbaloop macOS Installer ║${NC}" + echo -e "${GREEN}╚══════════════════════════════════════╝${NC}" + echo + + local os arch + os=$(detect_os) + arch=$(detect_arch) + + info "Platform: $os ($arch)" + + check_deps + + mkdir -p "$BIN_DIR" + mkdir -p "$INSTALL_DIR/configs" + + stop_services + + install_zenoh "$arch" + install_bridge "$arch" + + if [ "$skip_build" = true ]; then + if [ ! -x "$BIN_DIR/bubbaloop" ]; then + error "No bubbaloop binary found at $BIN_DIR/bubbaloop. Remove --skip-build to build from source." + fi + info "Skipping build (--skip-build)" + else + build_bubbaloop + fi + + setup_zenoh_config + setup_marketplace + setup_launchd + start_services + + local shell_rc + shell_rc=$(setup_path) + + echo + echo -e "${GREEN}╔══════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ Bubbaloop is ready! ║${NC}" + echo -e "${GREEN}╚══════════════════════════════════════╝${NC}" + echo + + if [ -x "$BIN_DIR/bubbaloop" ]; then + echo -e "${BLUE}Verifying installation...${NC}" + echo + "$BIN_DIR/bubbaloop" status 2>&1 || true + echo + fi + + echo -e "${YELLOW}To get started:${NC}" + echo + echo " source $shell_rc # Add bubbaloop to PATH (or open new terminal)" + echo " bubbaloop status # Check services" + echo " bubbaloop daemon # Run daemon directly" + echo " Open http://localhost:8080 for the dashboard" + echo +} + +main "$@"