Last active
March 2, 2025 16:38
-
-
Save jordanmessina/e5c895f1dbff721dc1ab3f7e278f2630 to your computer and use it in GitHub Desktop.
install.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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