/ 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 ""