#!/bin/bash # Ryvion DePIN Node — macOS Installer # Run: curl -sSL "https://ryvion-hub.fly.dev/install.sh?platform=macos" | bash set -euo pipefail HUB_URL=${HUB_URL:-"https://ryvion-hub.fly.dev"} BIND_TOKEN="" INSTALL_DIR="/usr/local/bin" DATA_DIR="$HOME/.ryvion" PLIST_PATH="$HOME/Library/LaunchAgents/com.ryvion.node.plist" UI_PORT="45890" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' INSTALL_WITH_SUDO=0 SERVICE_WAS_RUNNING=0 PROCESS_WAS_RUNNING=0 TMP_DIR="" cleanup() { if [ -n "${TMP_DIR:-}" ] && [ -d "$TMP_DIR" ]; then rm -rf "$TMP_DIR" fi } restore_previous_runtime() { exit_code=$? set +e cleanup if [ "$exit_code" -eq 0 ]; then return 0 fi echo "" echo -e "${RED} Installation failed before completion.${NC}" if [ "$SERVICE_WAS_RUNNING" = "1" ] && [ -f "$PLIST_PATH" ]; then echo -e "${YELLOW} Attempting to restore the previously running node service...${NC}" launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH" 2>/dev/null || launchctl load "$PLIST_PATH" 2>/dev/null || true launchctl kickstart -k "gui/$(id -u)/com.ryvion.node" 2>/dev/null || true elif [ "$PROCESS_WAS_RUNNING" = "1" ]; then echo -e "${YELLOW} The previous Ryvion node process was stopped during install. Re-run the installer after fixing the error.${NC}" fi exit "$exit_code" } trap restore_previous_runtime EXIT echo "" echo -e "${CYAN} Ryvion DePIN Node — macOS Installer${NC}" echo -e "${CYAN} =====================================${NC}" echo "" # ── Detect architecture ────────────────────────────────────────── ARCH=$(uname -m) case $ARCH in x86_64) BINARY_ARCH="amd64" ;; arm64) BINARY_ARCH="arm64" ;; *) echo -e "${RED} Unsupported architecture: $ARCH${NC}"; exit 1 ;; esac echo -e "${BLUE} Architecture: $ARCH${NC}" # ── Validate admin access before touching a running node ──────── if [ ! -w "$INSTALL_DIR" ]; then if ! command -v sudo >/dev/null 2>&1; then echo -e "${RED} sudo is required to install into $INSTALL_DIR${NC}" exit 1 fi echo -e "${YELLOW} [prep] Validating administrator access for $INSTALL_DIR...${NC}" echo -e "${BLUE} macOS may prompt for your password before any running node is stopped.${NC}" if ! sudo -v; then echo -e "${RED} Could not acquire administrator access. No changes were applied.${NC}" exit 1 fi INSTALL_WITH_SUDO=1 fi # ── Download binary ────────────────────────────────────────────── echo -e "${YELLOW} [1/4] Downloading binary...${NC}" mkdir -p "$DATA_DIR" DOWNLOAD_URL="${HUB_URL}/download/macos/binary?arch=${BINARY_ARCH}" TMP_DIR=$(mktemp -d) curl -fSL --retry 3 --connect-timeout 15 "$DOWNLOAD_URL" | tar -xz -C "$TMP_DIR" # Find the binary (may be at top level or inside a subdirectory) BINARY=$(find "$TMP_DIR" -name "ryvion-node" -type f | head -1) if [ -z "$BINARY" ]; then echo -e "${RED} Binary not found in archive${NC}" exit 1 fi echo -e "${GREEN} [OK] Binary downloaded${NC}" if [ "$INSTALL_WITH_SUDO" = "1" ] && ! sudo -v; then echo -e "${RED} Administrator access expired before install. No changes were applied.${NC}" exit 1 fi # ── Stop existing service only after the new binary is ready ──── if launchctl list 2>/dev/null | grep -q com.ryvion.node; then SERVICE_WAS_RUNNING=1 echo -e "${YELLOW} [prep] Stopping existing node...${NC}" launchctl bootout "gui/$(id -u)" "$PLIST_PATH" 2>/dev/null || launchctl unload "$PLIST_PATH" 2>/dev/null || true sleep 1 echo -e "${GREEN} [OK] Existing node stopped${NC}" elif pgrep -f ryvion-node > /dev/null 2>&1; then PROCESS_WAS_RUNNING=1 echo -e "${YELLOW} [prep] Stopping existing node process...${NC}" pkill -f ryvion-node 2>/dev/null || true sleep 1 echo -e "${GREEN} [OK] Existing node process stopped${NC}" fi # Install — prefer without sudo if possible if [ "$INSTALL_WITH_SUDO" = "1" ]; then sudo cp "$BINARY" "$INSTALL_DIR/ryvion-node" sudo chmod +x "$INSTALL_DIR/ryvion-node" else cp "$BINARY" "$INSTALL_DIR/ryvion-node" chmod +x "$INSTALL_DIR/ryvion-node" fi echo -e "${GREEN} [OK] Binary installed to $INSTALL_DIR/ryvion-node${NC}" # ── Write config ───────────────────────────────────────────────── echo -e "${YELLOW} [2/4] Writing configuration...${NC}" CONFIG_FILE="$DATA_DIR/config.json" if [ ! -f "$CONFIG_FILE" ]; then BIND_FIELD="" if [ -n "$BIND_TOKEN" ]; then BIND_FIELD=",\"bind_token\":\"$BIND_TOKEN\"" fi cat > "$CONFIG_FILE" << EOCFG {"hub_url":"$HUB_URL","device_type":"auto","log_level":"info"${BIND_FIELD}} EOCFG echo -e "${GREEN} [OK] Config created at $CONFIG_FILE${NC}" else echo -e "${GREEN} [OK] Config exists, keeping current settings${NC}" fi # ── Create launchd agent ───────────────────────────────────────── echo -e "${YELLOW} [3/4] Installing launch agent...${NC}" mkdir -p "$HOME/Library/LaunchAgents" cat > "$PLIST_PATH" << EOPLIST Label com.ryvion.node ProgramArguments $INSTALL_DIR/ryvion-node -ui-port $UI_PORT RunAtLoad KeepAlive EnvironmentVariables HOME $HOME RYV_HUB_URL $HUB_URL RYV_UI_PORT $UI_PORT RYV_BIND_TOKEN $BIND_TOKEN PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin StandardErrorPath $DATA_DIR/node.log StandardOutPath $DATA_DIR/node.log WorkingDirectory $DATA_DIR EOPLIST echo -e "${GREEN} [OK] Launch agent created${NC}" # ── Start service ──────────────────────────────────────────────── echo -e "${YELLOW} [4/4] Starting node...${NC}" launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH" 2>/dev/null || launchctl load "$PLIST_PATH" launchctl kickstart -k "gui/$(id -u)/com.ryvion.node" 2>/dev/null || true sleep 2 if launchctl list 2>/dev/null | grep -q com.ryvion.node; then echo -e "${GREEN} [OK] Node is running${NC}" else echo -e "${YELLOW} [!!] Could not verify service. Check: tail -f ~/.ryvion/node.log${NC}" fi if command -v curl >/dev/null 2>&1; then for _ in 1 2 3 4 5; do if curl -fsS "http://127.0.0.1:$UI_PORT/healthz" >/dev/null 2>&1; then echo -e "${GREEN} [OK] Local operator API is reachable on http://127.0.0.1:$UI_PORT${NC}" break fi sleep 1 done fi echo "" echo -e "${GREEN} Ryvion Node installed successfully!${NC}" echo "" echo -e " ${BLUE}tail -f ~/.ryvion/node.log${NC} View logs" echo -e " ${BLUE}launchctl stop com.ryvion.node${NC} Stop" echo -e " ${BLUE}launchctl start com.ryvion.node${NC} Start" echo -e " ${BLUE}launchctl unload ~/Library/LaunchAgents/com.ryvion.node.plist${NC} Uninstall" echo "" echo -e " Dashboard: ${BLUE}https://ryvion.com/dashboard${NC}" echo ""