/ install.sh
install.sh
1 #!/bin/bash 2 # InterBrain Installation Script 3 # For macOS/Linux systems 4 # One-command setup: curl -fsSL https://raw.githubusercontent.com/ProjectLiminality/InterBrain/main/install.sh | bash 5 # With URI: curl -fsSL https://raw.githubusercontent.com/ProjectLiminality/InterBrain/main/install.sh | bash -s -- --uri "obsidian://interbrain-clone?..." 6 7 set -e # Exit on error 8 9 echo "π InterBrain Installation Script" 10 echo "==================================" 11 echo "" 12 13 # Color output 14 GREEN='\033[0;32m' 15 YELLOW='\033[1;33m' 16 RED='\033[0;31m' 17 BLUE='\033[0;34m' 18 NC='\033[0m' # No Color 19 20 # Total number of steps (for progress tracking) 21 TOTAL_STEPS=13 22 23 # Default vault name and location 24 DEFAULT_VAULT_NAME="DreamVault" 25 DEFAULT_VAULT_PARENT="$HOME" 26 27 # Parse command-line arguments 28 CLONE_URI="" 29 while [[ $# -gt 0 ]]; do 30 case $1 in 31 --uri) 32 CLONE_URI="$2" 33 shift 2 34 ;; 35 *) 36 shift 37 ;; 38 esac 39 done 40 41 # Function to check if command exists 42 command_exists() { 43 command -v "$1" >/dev/null 2>&1 44 } 45 46 # Function to print success 47 success() { 48 echo -e "${GREEN}β $1${NC}" 49 } 50 51 # Function to print warning 52 warning() { 53 echo -e "${YELLOW}β οΈ $1${NC}" 54 } 55 56 # Function to print error 57 error() { 58 echo -e "${RED}β $1${NC}" 59 } 60 61 # Function to print info 62 info() { 63 echo -e "${BLUE}βΉοΈ $1${NC}" 64 } 65 66 # Function to show spinner during long operations 67 show_spinner() { 68 local pid=$1 69 local message=$2 70 local spin='β β β Ήβ Έβ Όβ ΄β ¦β §β β ' 71 local i=0 72 73 echo -n " " 74 while kill -0 $pid 2>/dev/null; do 75 i=$(( (i+1) %10 )) 76 printf "\r ${BLUE}${spin:$i:1}${NC} $message" 77 sleep .1 78 done 79 printf "\r β $message\n" 80 } 81 82 # Function to run command with spinner 83 run_with_spinner() { 84 local message=$1 85 shift 86 "$@" > /dev/null 2>&1 & 87 show_spinner $! "$message" 88 wait $! 89 } 90 91 # Function to refresh shell environment 92 refresh_shell_env() { 93 # Ensure newly installed binaries are available 94 hash -r 2>/dev/null || true 95 96 # Add common paths explicitly 97 export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.radicle/bin:$PATH" 98 99 # Source shell profile if it exists 100 if [ -f "$HOME/.zshrc" ]; then 101 source "$HOME/.zshrc" 2>/dev/null || true 102 elif [ -f "$HOME/.bashrc" ]; then 103 source "$HOME/.bashrc" 2>/dev/null || true 104 fi 105 } 106 107 echo "" 108 echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" 109 echo "Step 1/$TOTAL_STEPS: Checking prerequisites" 110 echo "ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" 111 112 # Check for Homebrew (macOS) 113 if [[ "$OSTYPE" == "darwin"* ]]; then 114 if ! command_exists brew; then 115 warning "Homebrew not found. Installing..." 116 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 117 118 # Add Homebrew to PATH based on architecture 119 if [[ $(uname -m) == 'arm64' ]]; then 120 eval "$(/opt/homebrew/bin/brew shellenv)" 121 else 122 eval "$(/usr/local/bin/brew shellenv)" 123 fi 124 125 refresh_shell_env 126 success "Homebrew installed" 127 else 128 success "Homebrew found" 129 fi 130 fi 131 132 # Check for Git 133 if ! command_exists git; then 134 echo "Installing Git..." 135 if [[ "$OSTYPE" == "darwin"* ]]; then 136 brew install git 137 refresh_shell_env 138 else 139 sudo apt-get update && sudo apt-get install -y git 140 fi 141 success "Git installed" 142 else 143 success "Git found ($(git --version))" 144 fi 145 146 # Check for Node.js 147 if ! command_exists node; then 148 echo "Installing Node.js..." 149 if [[ "$OSTYPE" == "darwin"* ]]; then 150 brew install node 151 refresh_shell_env 152 else 153 curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - 154 sudo apt-get install -y nodejs 155 fi 156 success "Node.js installed" 157 else 158 success "Node.js found ($(node --version))" 159 fi 160 161 # Check for Radicle 162 if ! command_exists rad; then 163 echo "Installing Radicle..." 164 curl -sSf https://radicle.xyz/install | sh 165 166 # Add Radicle to PATH for this session 167 export PATH="$HOME/.radicle/bin:$PATH" 168 refresh_shell_env 169 170 success "Radicle installed" 171 else 172 success "Radicle found ($(rad --version))" 173 fi 174 175 # Check for Obsidian 176 if [[ "$OSTYPE" == "darwin"* ]]; then 177 echo "" 178 echo "Step 2: Checking for Obsidian..." 179 echo "----------------------------------" 180 181 if [ -d "/Applications/Obsidian.app" ]; then 182 success "Obsidian found" 183 OBSIDIAN_INSTALLED=true 184 else 185 warning "Obsidian not found. Installing..." 186 brew install --cask obsidian 187 success "Obsidian installed" 188 OBSIDIAN_INSTALLED=true 189 fi 190 else 191 OBSIDIAN_INSTALLED=false 192 warning "Non-macOS system detected. Please install Obsidian manually from https://obsidian.md" 193 fi 194 195 echo "" 196 echo "Step 3: Setting up vault..." 197 echo "----------------------------" 198 199 # Check if user wants to use existing vault or create new one 200 if [ -t 0 ]; then 201 # Interactive mode (terminal attached) 202 echo "" 203 echo "Do you have an existing Obsidian vault?" 204 read -p "Press Enter to create a new vault, or type the path to your existing vault: " USER_VAULT_PATH 205 206 if [ -z "$USER_VAULT_PATH" ]; then 207 # Create new vault 208 echo "" 209 read -p "Vault name (default: $DEFAULT_VAULT_NAME): " VAULT_NAME 210 VAULT_NAME=${VAULT_NAME:-$DEFAULT_VAULT_NAME} 211 212 read -p "Location (default: $DEFAULT_VAULT_PARENT): " VAULT_PARENT 213 VAULT_PARENT=${VAULT_PARENT:-$DEFAULT_VAULT_PARENT} 214 215 VAULT_PATH="$VAULT_PARENT/$VAULT_NAME" 216 else 217 VAULT_PATH="$USER_VAULT_PATH" 218 fi 219 else 220 # Non-interactive mode (piped from curl) 221 VAULT_PATH="$DEFAULT_VAULT_PARENT/$DEFAULT_VAULT_NAME" 222 info "Non-interactive mode: Creating vault at $VAULT_PATH" 223 fi 224 225 # Create vault if it doesn't exist 226 if [ ! -d "$VAULT_PATH" ]; then 227 mkdir -p "$VAULT_PATH" 228 success "Created vault directory: $VAULT_PATH" 229 else 230 success "Using existing vault: $VAULT_PATH" 231 fi 232 233 # Create .obsidian directory structure if it doesn't exist 234 mkdir -p "$VAULT_PATH/.obsidian/plugins" 235 236 # Parse .gitignore and create Obsidian config with smart exclusions 237 info "Parsing .gitignore patterns for vault exclusions..." 238 239 # Generate userIgnoreFilters from .gitignore 240 INTERBRAIN_PATH="$VAULT_PATH/InterBrain" 241 GITIGNORE_PATH="$INTERBRAIN_PATH/.gitignore" 242 243 # Always exclude .git directory (not in .gitignore but critical) 244 FILTERS='["InterBrain/.git"' 245 246 if [ -f "$GITIGNORE_PATH" ]; then 247 # Parse .gitignore: filter comments, empty lines, and convert to JSON array 248 while IFS= read -r line || [ -n "$line" ]; do 249 # Skip empty lines and comments 250 if [ -z "$line" ] || [[ "$line" =~ ^[[:space:]]*# ]]; then 251 continue 252 fi 253 254 # Remove trailing slashes for consistency 255 pattern="${line%/}" 256 257 # Skip negation patterns (lines starting with !) 258 if [[ "$pattern" =~ ^! ]]; then 259 continue 260 fi 261 262 # Add InterBrain/ prefix and append to filters 263 FILTERS="$FILTERS, \"InterBrain/$pattern\"" 264 done < "$GITIGNORE_PATH" 265 fi 266 267 # Close JSON array 268 FILTERS="$FILTERS]" 269 270 # Create app.json with parsed filters 271 cat > "$VAULT_PATH/.obsidian/app.json" << EOF 272 { 273 "showLineNumber": true, 274 "spellcheck": true, 275 "promptDelete": false, 276 "userIgnoreFilters": $FILTERS 277 } 278 EOF 279 success "Created Obsidian config with $(echo "$FILTERS" | grep -o "InterBrain/" | wc -l | tr -d ' ') exclusion patterns from .gitignore" 280 281 # Create community-plugins.json to enable InterBrain plugin 282 cat > "$VAULT_PATH/.obsidian/community-plugins.json" << 'EOF' 283 ["interbrain"] 284 EOF 285 286 # Create snippets directory and enable InterBrain theme 287 mkdir -p "$VAULT_PATH/.obsidian/snippets" 288 289 # Create appearance.json with InterBrain theme enabled 290 cat > "$VAULT_PATH/.obsidian/appearance.json" << 'EOF' 291 { 292 "accentColor": "#00A2FF", 293 "theme": "obsidian", 294 "baseFontSize": 16, 295 "enabledCssSnippets": [ 296 "interbrain" 297 ] 298 } 299 EOF 300 success "Created theme configuration" 301 302 echo "" 303 echo "Step 4: Cloning InterBrain..." 304 echo "------------------------------" 305 306 # Clone into vault 307 INTERBRAIN_PATH="$VAULT_PATH/InterBrain" 308 309 if [ -d "$INTERBRAIN_PATH" ]; then 310 # Check if it's actually the InterBrain repo 311 if [ -d "$INTERBRAIN_PATH/.git" ]; then 312 cd "$INTERBRAIN_PATH" 313 REPO_URL=$(git config --get remote.origin.url 2>/dev/null || echo "") 314 315 if [[ "$REPO_URL" == *"ProjectLiminality/InterBrain"* ]]; then 316 warning "InterBrain already exists. Updating..." 317 git pull origin main 318 else 319 echo "" 320 error "Directory '$INTERBRAIN_PATH' already exists but is a different repository." 321 echo "" 322 echo "Please rename or move the existing directory, then run this script again:" 323 echo " mv '$INTERBRAIN_PATH' '$INTERBRAIN_PATH.backup'" 324 echo "" 325 exit 1 326 fi 327 else 328 echo "" 329 error "Directory '$INTERBRAIN_PATH' already exists but is not a git repository." 330 echo "" 331 echo "Please rename or move the existing directory, then run this script again:" 332 echo " mv '$INTERBRAIN_PATH' '$INTERBRAIN_PATH.backup'" 333 echo "" 334 exit 1 335 fi 336 else 337 echo "Cloning from GitHub..." 338 cd "$VAULT_PATH" 339 git clone https://github.com/ProjectLiminality/InterBrain.git 340 cd InterBrain 341 fi 342 343 success "InterBrain code ready at: $INTERBRAIN_PATH" 344 345 echo "" 346 echo "Step 5: Building plugin..." 347 echo "--------------------------" 348 349 cd "$INTERBRAIN_PATH" 350 351 # Install dependencies with spinner 352 npm install --silent > /dev/null 2>&1 & 353 show_spinner $! "Installing Node.js dependencies..." 354 wait $! 355 356 # Build with spinner 357 npm run build > /dev/null 2>&1 & 358 show_spinner $! "Building InterBrain plugin..." 359 wait $! 360 361 success "Plugin built successfully" 362 363 echo "" 364 echo "Step 6: Installing InterBrain theme..." 365 echo "---------------------------------------" 366 367 # Copy theme CSS to snippets directory 368 if [ -f "$INTERBRAIN_PATH/theme/interbrain.css" ]; then 369 cp "$INTERBRAIN_PATH/theme/interbrain.css" "$VAULT_PATH/.obsidian/snippets/" 370 success "InterBrain theme installed" 371 else 372 warning "Theme file not found, skipping theme installation" 373 fi 374 375 echo "" 376 echo "Step 7: Installing Ollama for semantic search..." 377 echo "-------------------------------------------------" 378 379 # Check for Ollama 380 if ! command_exists ollama; then 381 if [[ "$OSTYPE" == "darwin"* ]]; then 382 # macOS: Install via Homebrew 383 echo "Installing Ollama via Homebrew..." 384 brew install ollama 385 386 # Start Ollama service (Homebrew installs it as a service) 387 brew services start ollama 388 389 success "Ollama installed and service started" 390 else 391 # Linux: Use official install script 392 echo "Installing Ollama..." 393 curl -fsSL https://ollama.ai/install.sh | sh 394 395 # Add Ollama to PATH for this session 396 export PATH="$HOME/.ollama/bin:$PATH" 397 refresh_shell_env 398 399 success "Ollama installed" 400 fi 401 else 402 success "Ollama found ($(ollama --version 2>/dev/null || echo 'version unknown'))" 403 404 # Ensure Ollama service is running on macOS 405 if [[ "$OSTYPE" == "darwin"* ]]; then 406 if ! brew services list | grep -q "ollama.*started"; then 407 info "Starting Ollama service..." 408 brew services start ollama 409 fi 410 fi 411 fi 412 413 # Pull the embedding model 414 if ollama list 2>/dev/null | grep -q "nomic-embed-text"; then 415 success "nomic-embed-text model already installed" 416 else 417 info "Downloading nomic-embed-text model (this may take 1-2 minutes)..." 418 ollama pull nomic-embed-text > /dev/null 2>&1 & 419 show_spinner $! "Pulling nomic-embed-text model..." 420 wait $! 421 success "nomic-embed-text model installed" 422 fi 423 424 echo "" 425 echo "Step 8: Linking plugin to vault..." 426 echo "-----------------------------------" 427 428 PLUGINS_DIR="$VAULT_PATH/.obsidian/plugins" 429 mkdir -p "$PLUGINS_DIR" 430 431 SYMLINK_PATH="$PLUGINS_DIR/interbrain" 432 433 # Remove old symlink if exists 434 if [ -L "$SYMLINK_PATH" ]; then 435 rm "$SYMLINK_PATH" 436 warning "Removed old symlink" 437 fi 438 439 # Create symlink 440 ln -s "$INTERBRAIN_PATH" "$SYMLINK_PATH" 441 success "Symlink created: $SYMLINK_PATH β $INTERBRAIN_PATH" 442 443 echo "" 444 echo "Step 9: Radicle identity setup..." 445 echo "-----------------------------------" 446 447 # Check if Radicle identity exists 448 if rad self --did >/dev/null 2>&1; then 449 success "Radicle identity already exists" 450 RAD_DID=$(rad self --did) 451 RAD_ALIAS=$(rad self --alias 2>/dev/null || echo "Unknown") 452 echo " DID: $RAD_DID" 453 echo " Alias: $RAD_ALIAS" 454 else 455 warning "No Radicle identity found. Creating one..." 456 echo "" 457 echo "You will be asked to:" 458 echo " 1. Enter an alias (your name or nickname)" 459 echo " 2. Enter a passphrase (keep this safe!)" 460 echo "" 461 462 if [ -t 0 ]; then 463 read -p "Press Enter to continue..." 464 rad auth 465 else 466 echo "" 467 error "Cannot create Radicle identity in non-interactive mode." 468 echo "Please run this command after installation completes:" 469 echo " rad auth" 470 echo "" 471 RAD_DID="[Not created - run 'rad auth']" 472 RAD_ALIAS="[Not created]" 473 fi 474 475 if rad self --did >/dev/null 2>&1; then 476 success "Radicle identity created" 477 RAD_DID=$(rad self --did) 478 RAD_ALIAS=$(rad self --alias 2>/dev/null || echo "Unknown") 479 echo " DID: $RAD_DID" 480 echo " Alias: $RAD_ALIAS" 481 fi 482 fi 483 484 echo "" 485 echo "Step 10: Starting Radicle node..." 486 echo "----------------------------------" 487 488 # Check if node is already running 489 if rad node status 2>/dev/null | grep -qi "running"; then 490 success "Radicle node already running" 491 else 492 echo "Starting Radicle node..." 493 rad node start 494 sleep 2 495 success "Radicle node started" 496 fi 497 498 echo "" 499 echo "Step 11: Setting up real-time transcription (Python + Whisper)..." 500 echo "------------------------------------------------------------------" 501 502 # Check for Python 3 503 if ! command_exists python3; then 504 echo "Installing Python 3..." 505 if [[ "$OSTYPE" == "darwin"* ]]; then 506 brew install python3 507 refresh_shell_env 508 else 509 sudo apt-get update && sudo apt-get install -y python3 python3-pip 510 fi 511 success "Python 3 installed" 512 else 513 success "Python 3 found ($(python3 --version))" 514 fi 515 516 # Set up whisper_streaming virtual environment 517 TRANSCRIPTION_DIR="$INTERBRAIN_PATH/src/features/realtime-transcription/scripts" 518 if [ -d "$TRANSCRIPTION_DIR" ]; then 519 cd "$INTERBRAIN_PATH/src/features/realtime-transcription/scripts" 520 521 if [ ! -d "venv" ]; then 522 info "Setting up Python environment (this may take 1-2 minutes)..." 523 524 # Create venv 525 python3 -m venv venv > /dev/null 2>&1 & 526 show_spinner $! "Creating virtual environment..." 527 wait $! 528 529 # Install dependencies with spinner 530 ( 531 source venv/bin/activate 532 pip install --upgrade pip --quiet 533 pip install -r requirements.txt --quiet 534 deactivate 535 ) > /dev/null 2>&1 & 536 show_spinner $! "Installing whisper_streaming and dependencies..." 537 wait $! 538 539 success "Transcription environment ready" 540 else 541 success "Transcription environment already exists" 542 fi 543 else 544 warning "Transcription directory not found, skipping Python setup" 545 fi 546 547 echo "" 548 echo "Step 12: Final verification..." 549 echo "-------------------------------" 550 551 # Verify everything 552 ALL_GOOD=true 553 554 if [ ! -L "$SYMLINK_PATH" ]; then 555 error "Symlink not created" 556 ALL_GOOD=false 557 else 558 success "Plugin symlink exists" 559 fi 560 561 if [ ! -f "$INTERBRAIN_PATH/main.js" ]; then 562 error "Plugin not built (main.js missing)" 563 ALL_GOOD=false 564 else 565 success "Plugin built (main.js exists)" 566 fi 567 568 if ! rad node status 2>/dev/null | grep -qi "running"; then 569 warning "Radicle node not running (you can start it with: rad node start)" 570 else 571 success "Radicle node running" 572 fi 573 574 if [ "$OBSIDIAN_INSTALLED" = true ]; then 575 success "Obsidian installed" 576 fi 577 578 if command_exists ollama && ollama list 2>/dev/null | grep -q "nomic-embed-text"; then 579 success "Ollama with nomic-embed-text model ready" 580 else 581 warning "Ollama or embedding model not ready" 582 fi 583 584 if command_exists python3; then 585 success "Python 3 ready for transcription" 586 if [ -d "$TRANSCRIPTION_DIR/venv" ]; then 587 success "Whisper transcription environment ready" 588 else 589 warning "Whisper environment not set up" 590 fi 591 else 592 warning "Python 3 not installed (needed for transcription)" 593 fi 594 595 echo "" 596 echo "Step 13: Opening Obsidian..." 597 echo "-----------------------------" 598 599 if [[ "$OSTYPE" == "darwin"* ]] && [ "$OBSIDIAN_INSTALLED" = true ]; then 600 # Open Obsidian with the vault 601 info "Opening Obsidian to your DreamVault..." 602 sleep 1 603 open -a Obsidian "$VAULT_PATH" 604 success "Obsidian opened" 605 606 # If a clone URI was provided, trigger it after Obsidian loads 607 if [ -n "$CLONE_URI" ]; then 608 echo "" 609 info "Waiting for Obsidian and InterBrain to initialize..." 610 sleep 5 # Give Obsidian time to load the vault and enable plugins 611 612 info "Triggering clone URI..." 613 open "$CLONE_URI" 614 success "Clone URI triggered - DreamNode(s) will appear shortly" 615 echo "" 616 info "The clone operation is running in the background." 617 info "Watch for notifications in Obsidian about the clone progress." 618 fi 619 620 echo "" 621 info "Obsidian should open shortly. If not, open Obsidian and select:" 622 info " $VAULT_PATH" 623 fi 624 625 echo "" 626 echo "==================================" 627 if [ "$ALL_GOOD" = true ]; then 628 echo -e "${GREEN}β Installation complete!${NC}" 629 echo "" 630 if [ -n "$CLONE_URI" ]; then 631 echo "π― Personalized installation detected!" 632 echo "" 633 echo "The clone operation has been triggered automatically." 634 echo "Your collaborator's DreamNodes and Dreamer profile will appear in InterBrain." 635 echo "" 636 fi 637 echo "Next steps:" 638 echo "1. In Obsidian: Click 'Trust author and enable plugins' when prompted" 639 echo "2. Look for the InterBrain icon (π§ ) in the left ribbon" 640 echo "3. Use Command+R to reload the plugin during development" 641 echo "4. Run 'Full Index' command to enable semantic search" 642 if [ -z "$CLONE_URI" ]; then 643 echo "5. Click Clone URIs from your email to add DreamNodes" 644 fi 645 echo "" 646 echo "βοΈ IMPORTANT: Configure settings for full functionality:" 647 echo "" 648 echo "In Obsidian Settings β InterBrain:" 649 echo "" 650 echo "β’ Anthropic API Key - Required for AI features:" 651 echo " - Get your key from: https://console.anthropic.com/settings/keys" 652 echo " - Used for conversation summaries and semantic analysis" 653 echo "" 654 echo "β’ Radicle Passphrase - Required for seamless Radicle operations:" 655 echo " - This is the passphrase you created during 'rad auth'" 656 echo " - Enables automatic peer-to-peer syncing without password prompts" 657 echo " - Note: Stored locally in Obsidian's secure storage" 658 echo "" 659 if [[ "$RAD_DID" != "[Not created - run 'rad auth']" ]]; then 660 echo "Your Radicle identity:" 661 echo " DID: $RAD_DID" 662 echo " Alias: $RAD_ALIAS" 663 echo "" 664 echo "Share your DID with collaborators so they can follow you!" 665 fi 666 else 667 error "Installation completed with warnings. Please review the output above." 668 echo "" 669 echo "Common issues:" 670 echo "β’ If Radicle commands don't work, try: source ~/.zshrc" 671 echo "β’ If plugin doesn't appear, restart Obsidian" 672 echo "β’ If you need help, see: https://github.com/ProjectLiminality/InterBrain/issues" 673 fi 674 675 echo "" 676 echo "Happy dreaming! πβ¨" 677 echo ""