Skip to content

Instantly share code, notes, and snippets.

@jordanmessina
Last active March 2, 2025 16:38
Show Gist options
  • Save jordanmessina/e5c895f1dbff721dc1ab3f7e278f2630 to your computer and use it in GitHub Desktop.
Save jordanmessina/e5c895f1dbff721dc1ab3f7e278f2630 to your computer and use it in GitHub Desktop.
install.sh
#!/usr/bin/env bash
# Exit on error, unset variable, or pipeline failure; trap errors for logging
set -euo pipefail
trap 'error "Error on line $LINENO: $BASH_COMMAND"' ERR
# Logging helpers
info() { echo "[INFO] $*"; }
success() { echo "[SUCCESS] $*"; }
error() { echo "[ERROR] $*" >&2; }
################################################################################
# 1. Ensure script runs as root (via sudo or osascript with admin privileges)
################################################################################
if [[ "$EUID" -ne 0 ]]; then
error "Please run this script as root (sudo) or via osascript with administrator privileges."
exit 1
fi
################################################################################
# 2. Determine real user and paths
################################################################################
REAL_USER="${SUDO_USER:-$(whoami)}"
REAL_HOME="$(eval echo "~$REAL_USER")"
NASH_DIR="$REAL_HOME/Library/Application Support/Nash"
LOGS_DIR="$NASH_DIR/logs"
TIMESTAMP="$(date +'%Y%m%d-%H%M%S')"
LOG_FILE="$LOGS_DIR/${TIMESTAMP}-installation.log"
# Create directories as real user
sudo -u "$REAL_USER" mkdir -p "$NASH_DIR" "$LOGS_DIR"
# Create log file as the real user
sudo -u "$REAL_USER" touch "$LOG_FILE"
# Redirect all output to log file & console
exec > >(tee -a "$LOG_FILE") 2>&1
info "=== Starting installation script at $(date) ==="
info "Running as root. Real user is: $REAL_USER"
info "Real user's home directory: $REAL_HOME"
info "Nash directory: $NASH_DIR"
info "Log file: $LOG_FILE"
# Change working directory to avoid brew $PWD issues
cd "$REAL_HOME"
info "Changed working directory to: $(pwd)"
################################################################################
# 3. Confirm macOS Command Line Tools (if on macOS)
################################################################################
if [[ "$OSTYPE" == "darwin"* ]]; then
if ! xcode-select -p &>/dev/null; then
info "Xcode Command Line Tools not found. Attempting to install..."
# Try to install Xcode Command Line Tools
touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
PROD=$(softwareupdate -l | grep "\*.*Command Line" | head -n 1 | awk -F"*" '{print $2}' | sed -e 's/^ *//' | tr -d '\n')
softwareupdate -i "$PROD" --verbose || {
error "Failed to install Xcode Command Line Tools. Please install manually with 'xcode-select --install'."
info "Continuing with installation, but some features may not work correctly."
}
rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
fi
fi
################################################################################
# 4. Check for Python and try to use system Python if Homebrew is not available
################################################################################
USE_SYSTEM_PYTHON=false
SYSTEM_PYTHON=""
# Check for system Python
if command -v python3 &>/dev/null; then
SYSTEM_PYTHON=$(command -v python3)
info "Found system Python at: $SYSTEM_PYTHON"
elif command -v python &>/dev/null; then
SYSTEM_PYTHON=$(command -v python)
info "Found system Python at: $SYSTEM_PYTHON"
fi
# Try to install Homebrew if it's not already installed
if ! sudo -u "$REAL_USER" command -v brew &>/dev/null; then
info "Homebrew not found. Attempting to install..."
# Check if user is an admin before trying to install Homebrew
if groups "$REAL_USER" | grep -q -w admin; then
info "User $REAL_USER is an administrator. Proceeding with Homebrew installation..."
sudo -u "$REAL_USER" bash -c "curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash" || {
error "Failed to install Homebrew. User may not have sufficient permissions."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
}
else
info "User $REAL_USER is not an administrator. Skipping Homebrew installation."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
fi
if ! $USE_SYSTEM_PYTHON; then
# Attempt to source brew shellenv
if [[ -f "/opt/homebrew/bin/brew" ]]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
BREW_PATH="/opt/homebrew/bin/brew"
elif [[ -f "/usr/local/bin/brew" ]]; then
eval "$(/usr/local/bin/brew shellenv)"
BREW_PATH="/usr/local/bin/brew"
else
error "Homebrew installed but cannot find 'brew' in expected locations."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
fi
if ! $USE_SYSTEM_PYTHON; then
success "Homebrew installed."
fi
fi
else
info "Homebrew is already installed for $REAL_USER."
if sudo -u "$REAL_USER" test -f "/opt/homebrew/bin/brew"; then
BREW_PATH="/opt/homebrew/bin/brew"
elif sudo -u "$REAL_USER" test -f "/usr/local/bin/brew"; then
BREW_PATH="/usr/local/bin/brew"
else
error "brew not found in /opt/homebrew/bin or /usr/local/bin."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
fi
if ! $USE_SYSTEM_PYTHON; then
info "Using Homebrew at: $BREW_PATH"
fi
fi
# If using Homebrew, update it and install dependencies
if ! $USE_SYSTEM_PYTHON; then
info "Updating Homebrew as $REAL_USER..."
sudo -u "$REAL_USER" bash -c "cd \"$REAL_HOME\" && \"$BREW_PATH\" update" || {
info "Homebrew update failed. Continuing anyway..."
}
################################################################################
# 5. Install pyenv & build dependencies if using Homebrew
################################################################################
brew_deps=(openssl readline sqlite3 xz zlib tcl-tk)
for dep in "${brew_deps[@]}"; do
info "Ensuring dependency: $dep"
sudo -u "$REAL_USER" bash -c "cd \"$REAL_HOME\" && \"$BREW_PATH\" install \"$dep\"" || {
info "Failed to install $dep. Continuing anyway..."
}
done
if ! sudo -u "$REAL_USER" command -v pyenv &>/dev/null; then
info "pyenv not found. Installing pyenv..."
sudo -u "$REAL_USER" bash -c "cd \"$REAL_HOME\" && \"$BREW_PATH\" install pyenv" || {
error "Failed to install pyenv."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
}
if ! $USE_SYSTEM_PYTHON; then
success "pyenv installed."
fi
else
info "pyenv is already installed for $REAL_USER."
fi
################################################################################
# 6. Install or skip Python 3.11 with pyenv if using Homebrew
################################################################################
if ! $USE_SYSTEM_PYTHON; then
TARGET_PYTHON_VERSION="3.11"
# Locate pyenv bin
if sudo -u "$REAL_USER" test -f "/opt/homebrew/bin/pyenv"; then
PYENV_BIN="/opt/homebrew/bin/pyenv"
elif sudo -u "$REAL_USER" test -f "/usr/local/bin/pyenv"; then
PYENV_BIN="/usr/local/bin/pyenv"
elif sudo -u "$REAL_USER" test -f "$REAL_HOME/.pyenv/bin/pyenv"; then
PYENV_BIN="$REAL_HOME/.pyenv/bin/pyenv"
else
error "pyenv executable not found in expected locations."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
fi
if ! $USE_SYSTEM_PYTHON; then
info "Using pyenv at: $PYENV_BIN"
info "Installing (or skipping) Python $TARGET_PYTHON_VERSION via pyenv..."
OPENSSL_PREFIX="$(sudo -u "$REAL_USER" bash -c "cd \"$REAL_HOME\" && \"$BREW_PATH\" --prefix openssl@3 || \"$BREW_PATH\" --prefix openssl")"
info "Using OpenSSL from: $OPENSSL_PREFIX"
# -s or --skip-existing: if Python version folder exists, skip
sudo -u "$REAL_USER" bash -c "cd \"$REAL_HOME\" && CONFIGURE_OPTS=\"--with-openssl=$OPENSSL_PREFIX\" \"$PYENV_BIN\" install -s \"$TARGET_PYTHON_VERSION\"" || {
error "pyenv installation of Python $TARGET_PYTHON_VERSION failed."
if [ -n "$SYSTEM_PYTHON" ]; then
info "Will use system Python instead: $SYSTEM_PYTHON"
USE_SYSTEM_PYTHON=true
else
error "No system Python found. Cannot continue installation."
exit 1
fi
}
if ! $USE_SYSTEM_PYTHON; then
success "Python $TARGET_PYTHON_VERSION installed or already existed."
# Determine the actual installed subversion (e.g., 3.11.11)
INSTALLED_VERSION="$(sudo -u "$REAL_USER" "$PYENV_BIN" versions --bare \
| grep -E "^$TARGET_PYTHON_VERSION" | sort -Vr | head -n1)"
USER_PYENV_ROOT="$(sudo -u "$REAL_USER" "$PYENV_BIN" root)"
PYTHON_EXE="$USER_PYENV_ROOT/versions/$INSTALLED_VERSION/bin/python"
info "Using Python at: $PYTHON_EXE"
fi
fi
fi
fi
################################################################################
# 7. Download and set up Nash MCP repository
################################################################################
NASH_MCP_ZIP_URL="https://github.com/nash-app/nash-mcp/archive/refs/tags/v0.1.3.zip"
NASH_MCP_ZIP="$NASH_DIR/nash-mcp-v0.1.3.zip"
NASH_MCP_EXTRACT_DIR="$NASH_DIR/nash-mcp-0.1.3"
info "Downloading Nash MCP repository..."
sudo -u "$REAL_USER" curl -L "$NASH_MCP_ZIP_URL" -o "$NASH_MCP_ZIP" \
|| { error "Failed to download Nash MCP repository."; exit 1; }
success "Downloaded Nash MCP repository to $NASH_MCP_ZIP"
info "Unzipping Nash MCP repository..."
sudo -u "$REAL_USER" unzip -q -o "$NASH_MCP_ZIP" -d "$NASH_DIR" \
|| { error "Failed to unzip Nash MCP repository."; exit 1; }
success "Unzipped Nash MCP repository to $NASH_DIR"
# Find the actual directory name after extraction
NASH_MCP_DIR=$(find "$NASH_DIR" -maxdepth 1 -type d -name "nash-mcp*" | head -n 1)
if [ -z "$NASH_MCP_DIR" ]; then
error "Could not find the extracted Nash MCP directory."
exit 1
fi
info "Found Nash MCP directory at: $NASH_MCP_DIR"
info "Removing zip file..."
sudo -u "$REAL_USER" rm "$NASH_MCP_ZIP" \
|| { error "Failed to remove Nash MCP zip file."; exit 1; }
success "Removed Nash MCP zip file"
################################################################################
# 8. Create a virtual environment in the Nash MCP directory
################################################################################
VENV_PATH="$NASH_MCP_DIR/.venv"
info "Ensuring venv at: $VENV_PATH"
if $USE_SYSTEM_PYTHON; then
# Use system Python to create the virtual environment
info "Creating virtual environment with system Python..."
sudo -u "$REAL_USER" "$SYSTEM_PYTHON" -m venv "$VENV_PATH" \
|| { error "Failed to create venv with system Python."; exit 1; }
success "Virtual environment created with system Python at: $VENV_PATH"
else
# Use pyenv Python to create the virtual environment
if [[ ! -d "$VENV_PATH" ]]; then
info "Creating new virtual environment with Python $INSTALLED_VERSION..."
sudo -u "$REAL_USER" "$PYTHON_EXE" -m venv "$VENV_PATH" \
|| { error "Failed to create venv at $VENV_PATH."; exit 1; }
success "Virtual environment created at: $VENV_PATH"
else
info "Virtual environment already exists at: $VENV_PATH. Skipping creation."
fi
fi
info "Upgrading pip, setuptools, wheel in the virtual environment..."
sudo -u "$REAL_USER" "$VENV_PATH/bin/pip" install --upgrade pip setuptools wheel \
|| { error "Failed to upgrade pip, setuptools, wheel in venv."; exit 1; }
success "Upgraded pip, setuptools, wheel in the venv."
################################################################################
# 9. Install Poetry and project dependencies
################################################################################
info "Installing Poetry in the virtual environment..."
sudo -u "$REAL_USER" "$VENV_PATH/bin/pip" install poetry \
|| { error "Failed to install Poetry in venv."; exit 1; }
success "Installed Poetry in the virtual environment."
info "Installing project dependencies with Poetry..."
cd "$NASH_MCP_DIR"
# First check if pyproject.toml exists
if [ -f "$NASH_MCP_DIR/pyproject.toml" ]; then
# Try to install dependencies with Poetry, but don't fail if it doesn't work
sudo -u "$REAL_USER" bash -c "cd \"$NASH_MCP_DIR\" && \"$VENV_PATH/bin/poetry\" config virtualenvs.create false && \"$VENV_PATH/bin/poetry\" install --no-interaction" || {
info "Poetry install encountered issues, trying alternative installation method..."
# If poetry install fails, try to install requirements directly if requirements.txt exists
if [ -f "$NASH_MCP_DIR/requirements.txt" ]; then
sudo -u "$REAL_USER" "$VENV_PATH/bin/pip" install -r "$NASH_MCP_DIR/requirements.txt" \
|| { error "Failed to install requirements from requirements.txt"; exit 1; }
success "Installed dependencies from requirements.txt"
else
info "No requirements.txt found, continuing without installing additional dependencies."
fi
}
else
info "No pyproject.toml found, checking for requirements.txt..."
# If no pyproject.toml, try requirements.txt
if [ -f "$NASH_MCP_DIR/requirements.txt" ]; then
sudo -u "$REAL_USER" "$VENV_PATH/bin/pip" install -r "$NASH_MCP_DIR/requirements.txt" \
|| { error "Failed to install requirements from requirements.txt"; exit 1; }
success "Installed dependencies from requirements.txt"
else
info "No dependency files found, continuing without installing additional dependencies."
fi
fi
################################################################################
# 10. Final ownership fix and summary
################################################################################
info "Ensuring all files in $NASH_DIR are owned by $REAL_USER..."
chown -R "$REAL_USER" "$NASH_DIR"
info "=== Installation script finished at $(date) ==="
success "Nash MCP repository has been set up at: $NASH_MCP_DIR"
if $USE_SYSTEM_PYTHON; then
success "Using system Python for installation."
else
success "Python $TARGET_PYTHON_VERSION is installed."
fi
info "Virtual environment: $VENV_PATH"
info "You can run: \"$VENV_PATH/bin/python\" --version"
info "Log file: $LOG_FILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment