#!/bin/sh # Soomin's Auto Rice Bootstrapping Script (THESIAH) adapted for macOS # Adaptation by: Soomin Im # License: GNU GPLv3 set -e # Exit on error ### VARIABLES ### dotfilesrepo="https://github.com/TheSiahxyz/.dotfiles.git" progsfile="thesiah.xyz/macprogs.csv" repobranch="master" ### ENVIRONMENT VARIABLES FOR NON-INTERACTIVE BREW ### export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_NO_AUTO_UPDATE=1 export HOMEBREW_NO_ANALYTICS=1 export HOMEBREW_NO_ENV_HINTS=1 ### FUNCTIONS ### # Print formatted output print_header() { echo "==================================================" echo "$1" echo "==================================================" } print_step() { echo " -> $1" } print_success() { echo " [OK] $1" } print_error() { echo " [ERROR] $1" >&2 } print_warning() { echo " [WARNING] $1" } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Check if directory exists and is writable check_directory() { if [ ! -d "$1" ]; then print_step "Creating directory: $1" mkdir -p "$1" || error "Failed to create directory: $1" fi if [ ! -w "$1" ]; then error "Directory is not writable: $1" fi } # Notify function using macOS's notification system notify() { if command_exists osascript; then osascript -e "display notification \"$2\" with title \"$1\"" 2>/dev/null || true fi } # Error function for error handling and notification error() { print_error "$1" notify "Installation Failed" "$1" exit 1 } # Install Homebrew brewinstall() { print_step "Checking Homebrew installation..." if ! command_exists brew; then print_step "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # Add Homebrew to PATH for Apple Silicon Macs if [ -f "/opt/homebrew/bin/brew" ]; then eval "$(/opt/homebrew/bin/brew shellenv)" elif [ -f "/usr/local/bin/brew" ]; then eval "$(/usr/local/bin/brew shellenv)" fi # Verify brew is now available if ! command_exists brew; then error "Homebrew installation failed - brew command not found" fi else print_success "Homebrew already installed" fi print_step "Updating Homebrew..." brew update >/dev/null 2>&1 || print_warning "Homebrew update failed, continuing..." brew upgrade >/dev/null 2>&1 || print_warning "Homebrew upgrade failed, continuing..." } # Install Homebrew packages with non-interactive flags installpkg() { pkg="$1" if brew list "$pkg" >/dev/null 2>&1; then print_success "$pkg already installed" else print_step "Installing $pkg..." # Use non-interactive flags to prevent user input prompts brew install --force-bottle --no-quarantine "$pkg" 2>/dev/null || \ brew install "$pkg" || error "Failed to install $pkg" print_success "$pkg installed successfully" fi } # Install Homebrew Cask applications caskinstall() { pkg="$1" if brew list --cask "$pkg" >/dev/null 2>&1; then print_success "$pkg already installed" else print_step "Installing $pkg..." # Use non-interactive flags for cask installations brew install --cask --force --no-quarantine "$pkg" 2>/dev/null || \ brew install --cask "$pkg" || error "Failed to install $pkg" print_success "$pkg installed successfully" fi } # Install Python packages with pip pipinstall() { pkg="$1" print_step "Installing Python package: $pkg" if ! command_exists pip3; then print_step "Installing Python (includes pip3)..." installpkg python fi # Use --quiet flag to prevent pip from asking questions pip3 install --quiet "$pkg" || error "Failed to install Python package $pkg" print_success "$pkg installed successfully" } # Install Homebrew Tap packages tapinstall() { repo=$(echo "$1" | cut -d'/' -f1) pkg=$(echo "$1" | cut -d'/' -f2) print_step "Adding tap: $repo" brew tap "$repo" || error "Failed to tap $repo" print_step "Installing package: $pkg from tap" # Use non-interactive flags for tap packages too brew install --force-bottle --no-quarantine "$pkg" 2>/dev/null || \ brew install "$pkg" || error "Failed to install $pkg from tap" print_success "$pkg installed successfully" } # Install mac apps with mas masinstall() { app_name="$1" print_step "Installing Mac App Store app: $app_name" # Search for the app by name and get its ID id=$(mas search "$app_name" | awk -v appName="$app_name" ' tolower($0) ~ tolower(appName) { print $1; exit } ') if [ -z "$id" ]; then error "Failed to find an ID for the app named $app_name" else print_step "Installing app ID: $id" mas install "$id" || error "Failed to install the app $app_name" print_success "$app_name installed successfully" fi } # Clone and setup dotfiles putgitrepo() { repo="$1" target="$2" branch="${3:-$repobranch}" print_step "Downloading and installing config files..." notify "Installation" "Downloading and installing config files..." # Check if git is available if ! command_exists git; then error "Git is not installed. Please install git first." fi # Check target directory check_directory "$target" dir=$(mktemp -d) print_step "Cloning repository: $repo" git clone --depth 1 --single-branch --no-tags -q --recursive -b "$branch" "$repo" "$dir" || \ error "Failed to clone $repo" print_step "Copying files to target directory" cp -Rf "$dir/"* "$target" 2>/dev/null || true rm -rf "$dir" print_success "Config files installed successfully" } # Installation loop for processing the programs.csv file installationloop() { csv_file="/tmp/programs.csv" print_header "Installing Software Packages" # Download programs file if [ -f "$progsfile" ]; then print_step "Using local programs file: $progsfile" cp "$progsfile" "$csv_file" else print_step "Downloading programs list from: $progsfile" if ! curl -Ls "$progsfile" | sed '/^#/d' > "$csv_file"; then error "Failed to download programs file from $progsfile" fi # Verify file was downloaded and has content if [ ! -s "$csv_file" ]; then error "Downloaded programs file is empty" fi fi # Count total packages for progress total_packages=$(wc -l < "$csv_file" 2>/dev/null | tr -d ' ' || echo "0") if [ "$total_packages" -eq 0 ]; then error "No packages found in programs file" fi current=0 while IFS=, read -r tag program comment; do # Skip empty lines if [ -z "$tag" ] || [ -z "$program" ]; then continue fi current=$((current + 1)) echo "" print_step "[$current/$total_packages] $comment" case "$tag" in "P") pipinstall "$program" ;; "C") caskinstall "$program" ;; "M") masinstall "$program" ;; "T") tapinstall "$program" ;; *) installpkg "$program" ;; esac done < "$csv_file" rm -f "$csv_file" print_success "All packages installed successfully" } # Setup shell configuration setup_shell() { print_header "Setting up Shell Configuration" print_step "Creating symbolic links for shell configs..." # Create backup of existing configs timestamp=$(date +%Y%m%d_%H%M%S) [ -f ~/.zprofile ] && mv ~/.zprofile ~/.zprofile.backup.$timestamp [ -f ~/.bash_profile ] && mv ~/.bash_profile ~/.bash_profile.backup.$timestamp [ -f ~/.bashrc ] && mv ~/.bashrc ~/.bashrc.backup.$timestamp # Create new symbolic links ln -sf ~/.dotfiles/mac/.config/shell/profile ~/.zprofile ln -sf ~/.dotfiles/mac/.config/bash/bash_profile ~/.bash_profile ln -sf ~/.dotfiles/mac/.config/bash/bashrc ~/.bashrc print_success "Shell configuration setup completed" } # Setup dotfiles with stow setup_dotfiles() { print_header "Setting up Dotfiles" print_step "Installing dotfiles with stow..." # Check if stow is available if ! command_exists stow; then print_warning "Stow not found, installing..." installpkg stow fi # Check if dotfiles directory exists if [ ! -d ~/.dotfiles ]; then error "Dotfiles directory not found: ~/.dotfiles" fi cd ~/.dotfiles || error "Failed to change to dotfiles directory" # Install global configurations print_step "Installing global configurations..." if [ -d "global" ]; then stow --no-folding -S global || error "Failed to stow global configs" else print_warning "Global configs directory not found, skipping..." fi # Install macOS specific configurations print_step "Installing macOS specific configurations..." if [ -d "mac" ]; then stow --no-folding -S mac || error "Failed to stow mac configs" else print_warning "Mac configs directory not found, skipping..." fi cd - > /dev/null print_success "Dotfiles setup completed" } # Finalize installation finalize() { print_header "Installation Complete!" echo "" echo "Congratulations! The installation has completed successfully." echo "" echo "What was installed:" echo " - Homebrew and essential packages" echo " - Mac App Store applications" echo " - Python packages" echo " - Shell configuration files" echo " - Dotfiles and system configurations" echo "" echo "Next steps:" echo " - Restart your terminal or run: source ~/.zprofile" echo " - Customize your configuration files in ~/.dotfiles" echo " - Install additional packages as needed" echo "" echo "For support, visit: https://github.com/TheSiahxyz/.dotfiles" echo "" echo "Best regards," echo "Soomin" echo "" notify "Installation Complete" "THESIAH setup finished successfully!" } ### MAIN INSTALLATION PROCESS ### main() { print_header "THESIAH - macOS Auto Setup Script" echo "Starting installation process..." echo "" # Check if running on macOS if [ "$(uname)" != "Darwin" ]; then error "This script is designed for macOS only" fi # Check if running as root if [ "$(id -u)" -eq 0 ]; then error "This script should not be run as root" fi # Ensure Homebrew is installed brewinstall || error "Failed to install Homebrew" # Ensure mas is installed installpkg "mas" # Start the installation loop for software from the CSV installationloop # Setup Dotfiles putgitrepo "$dotfilesrepo" "$HOME" "$repobranch" # Move dotfiles to proper location dotfiles_dir=$(basename "$dotfilesrepo" .git) if [ -d "$HOME/$dotfiles_dir" ]; then mv "$HOME/$dotfiles_dir" "$HOME/.dotfiles" fi # Setup shell configuration setup_shell # Setup dotfiles with stow setup_dotfiles # Finalize finalize } # Run main function main "$@"