237 lines
7.4 KiB
Bash
Executable File
237 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Mike's Dotfiles Install Script
|
|
# Safe to run multiple times (idempotent).
|
|
# Targets both macOS and Linux (Coder devboxes).
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration ---
|
|
export HOMEBREW_TEMP="/tmp"
|
|
|
|
# Optional components (default to true)
|
|
INSTALL_NODE=${INSTALL_NODE:-true}
|
|
INSTALL_PYTHON=${INSTALL_PYTHON:-true}
|
|
INSTALL_GO=${INSTALL_GO:-true}
|
|
INSTALL_JAVA=${INSTALL_JAVA:-true}
|
|
|
|
BREW_PACKAGES=(
|
|
git diff-so-fancy btop gh zsh starship stow bat fd jq wget eza
|
|
git-lfs difftastic fzf direnv git-delta zsh-completions
|
|
gemini-cli claude-code zsh-autosuggestions zsh-syntax-highlighting
|
|
zsh-history-substring-search zoxide ripgrep
|
|
)
|
|
|
|
# Only install tmux if not on macOS
|
|
[[ "$(uname)" != "Darwin" ]] && BREW_PACKAGES+=(tmux)
|
|
|
|
# Add optional packages to Homebrew list
|
|
[[ "$INSTALL_GO" == "true" ]] && BREW_PACKAGES+=(go)
|
|
[[ "$INSTALL_JAVA" == "true" ]] && BREW_PACKAGES+=(openjdk)
|
|
[[ "$INSTALL_PYTHON" == "true" ]] && BREW_PACKAGES+=(pyenv)
|
|
|
|
MAC_CASK_PACKAGES=(iterm2 font-hack-nerd-font)
|
|
|
|
STOW_PACKAGES=(editorconfig gh git claude cursor starship zsh)
|
|
|
|
# --- Utilities ---
|
|
info() { echo -e "\033[0;34m[INFO]\033[0m $*"; }
|
|
warn() { echo -e "\033[0;33m[WARN]\033[0m $*"; }
|
|
error() { echo -e "\033[0;31m[ERROR]\033[0m $*"; exit 1; }
|
|
|
|
OS_TYPE=$(uname)
|
|
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# --- macOS System Setup ---
|
|
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
info "Configuring macOS system defaults..."
|
|
defaults write com.apple.finder AppleShowAllFiles YES
|
|
defaults write com.apple.finder ShowPathbar -bool true
|
|
defaults write com.apple.finder ShowStatusBar -bool true
|
|
# Refresh Finder
|
|
killall Finder 2>/dev/null || true
|
|
fi
|
|
|
|
# --- Homebrew Setup ---
|
|
if ! command -v brew &> /dev/null; then
|
|
info "Installing Homebrew..."
|
|
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
fi
|
|
|
|
# Initialize Homebrew environment for the current script
|
|
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
eval "$(/opt/homebrew/bin/brew shellenv)" 2>/dev/null || eval "$(/usr/local/bin/brew shellenv)" 2>/dev/null
|
|
ZSH_PATH="$(brew --prefix)/bin/zsh"
|
|
else
|
|
# Linux / Coder
|
|
if [ -d "/home/linuxbrew/.linuxbrew" ]; then
|
|
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
|
fi
|
|
ZSH_PATH="/home/linuxbrew/.linuxbrew/bin/zsh"
|
|
fi
|
|
|
|
BREW_BIN=$(command -v brew)
|
|
|
|
# --- Package Installation ---
|
|
info "Installing Homebrew packages..."
|
|
$BREW_BIN install "${BREW_PACKAGES[@]}" || warn "Some packages might have failed to install or are already present."
|
|
|
|
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
info "Installing macOS casks..."
|
|
$BREW_BIN install --cask "${MAC_CASK_PACKAGES[@]}" || warn "Some casks might have failed to install."
|
|
|
|
# Permissions fix for completions
|
|
chmod go-w "$(brew --prefix)/share" 2>/dev/null || true
|
|
chmod -R go-w "$(brew --prefix)/share/zsh" 2>/dev/null || true
|
|
fi
|
|
|
|
# --- Shell Setup ---
|
|
info "Setting up Zsh..."
|
|
|
|
# Add to /etc/shells if missing
|
|
if ! grep -q "$ZSH_PATH" /etc/shells; then
|
|
info "Adding $ZSH_PATH to /etc/shells"
|
|
if command -v sudo &> /dev/null; then
|
|
echo "$ZSH_PATH" | sudo tee -a /etc/shells
|
|
else
|
|
warn "Sudo not found, skipping /etc/shells update"
|
|
fi
|
|
fi
|
|
|
|
# Change default shell
|
|
CURRENT_LOGIN_SHELL=""
|
|
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
CURRENT_LOGIN_SHELL=$(dscl . -read "/Users/$USER" UserShell | awk '{print $2}')
|
|
else
|
|
CURRENT_LOGIN_SHELL=$(getent passwd "$USER" | cut -d: -f7)
|
|
fi
|
|
|
|
if [[ "$CURRENT_LOGIN_SHELL" != "$ZSH_PATH" ]]; then
|
|
info "Changing default shell to $ZSH_PATH"
|
|
if command -v sudo &> /dev/null; then
|
|
sudo chsh -s "$ZSH_PATH" "$USER" || warn "Failed to change shell with sudo"
|
|
else
|
|
warn "Sudo not found, skipping shell change"
|
|
fi
|
|
fi
|
|
|
|
# Oh My Zsh
|
|
if [ ! -d "$HOME/.oh-my-zsh" ]; then
|
|
info "Installing Oh My Zsh..."
|
|
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
|
|
fi
|
|
|
|
# --- Runtime Environments ---
|
|
# NVM / Node
|
|
if [[ "$INSTALL_NODE" == "true" ]]; then
|
|
info "Configuring NVM..."
|
|
export NVM_DIR="$HOME/.nvm"
|
|
if [ ! -d "$NVM_DIR" ]; then
|
|
info "Installing NVM via official script..."
|
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
|
fi
|
|
|
|
# Remove incompatible prefix/globalconfig from .npmrc for nvm compatibility
|
|
if [ -f "$HOME/.npmrc" ]; then
|
|
info "Cleaning .npmrc of incompatible settings..."
|
|
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
sed -i '' '/^prefix=/d; /^globalconfig=/d' "$HOME/.npmrc"
|
|
else
|
|
sed -i '/^prefix=/d; /^globalconfig=/d' "$HOME/.npmrc"
|
|
fi
|
|
fi
|
|
|
|
# shellcheck source=/dev/null
|
|
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
|
source "$NVM_DIR/nvm.sh"
|
|
nvm install --lts
|
|
npm install -g npm@latest
|
|
fi
|
|
fi
|
|
|
|
# Pyenv / Python
|
|
if [[ "$INSTALL_PYTHON" == "true" ]] && command -v pyenv &> /dev/null; then
|
|
info "Configuring Pyenv..."
|
|
eval "$(pyenv init -)"
|
|
pyenv install 3.10 -s
|
|
pyenv global 3.10
|
|
fi
|
|
|
|
# FZF Setup
|
|
if command -v fzf &> /dev/null; then
|
|
info "Configuring FZF..."
|
|
"$(brew --prefix)/opt/fzf/install" --key-bindings --completion --no-update-rc --no-bash --no-zsh || true
|
|
fi
|
|
|
|
# --- AI Rules Generation ---
|
|
generate_rules() {
|
|
local target_dir="$DOTFILES_DIR/$1"
|
|
info "Generating rules in $target_dir"
|
|
mkdir -p "$target_dir"
|
|
# Remove stale symlinks
|
|
find "$target_dir" -type l -delete
|
|
# Create new symlinks
|
|
for rule in "$DOTFILES_DIR/ai-instructions"/*.md; do
|
|
[ -e "$rule" ] || continue
|
|
ln -sf "$rule" "$target_dir/$(basename "$rule")"
|
|
done
|
|
}
|
|
|
|
generate_rules "claude/.claude/rules"
|
|
generate_rules "cursor/.cursor/rules"
|
|
|
|
# --- Stow ---
|
|
info "Applying dotfile configurations via Stow..."
|
|
for pkg in "${STOW_PACKAGES[@]}"; do
|
|
info "Stowing $pkg..."
|
|
# Detect conflicts and remove non-stow files
|
|
stow --simulate "$pkg" -t "$HOME" -d "$DOTFILES_DIR" 2>&1 | \
|
|
grep -E "(existing target is (not owned by stow|neither a link nor a directory):|over existing target .+ since neither a link)" | \
|
|
awk '/over existing target/ { for(i=1;i<=NF;i++) if($i=="target") { print $(i+1); break } next } /:/ { sub(/.*: /, ""); print }' | \
|
|
while read -r conflict; do
|
|
if [ -n "$conflict" ]; then
|
|
warn "Removing conflict: $HOME/$conflict"
|
|
rm -rf "$HOME/$conflict"
|
|
fi
|
|
done || true
|
|
stow "$pkg" -t "$HOME" -d "$DOTFILES_DIR"
|
|
done
|
|
|
|
# --- Git Local Config ---
|
|
info "Configuring machine-specific Git settings..."
|
|
GIT_LOCAL_CONFIG="$HOME/.gitconfig_local"
|
|
WORK_DIR="$HOME/work"
|
|
|
|
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
|
GH_PATH="/opt/homebrew/bin/gh"
|
|
else
|
|
GH_PATH="/home/linuxbrew/.linuxbrew/bin/gh"
|
|
fi
|
|
|
|
# Set credential helper
|
|
git config --file "$GIT_LOCAL_CONFIG" credential."https://github.com".helper "!$GH_PATH auth git-credential"
|
|
git config --file "$GIT_LOCAL_CONFIG" credential."https://gist.github.com".helper "!$GH_PATH auth git-credential"
|
|
|
|
# --- Final Tools & Linux Extras ---
|
|
if [[ "$OS_TYPE" == "Linux" ]]; then
|
|
if command -v apt-get &> /dev/null; then
|
|
info "Installing nano via apt..."
|
|
sudo apt-get update -qq && sudo apt-get install -y -qq nano || warn "Failed to install nano"
|
|
fi
|
|
|
|
# Bash auto-switch to zsh
|
|
if ! grep -q "exec \"$ZSH_PATH\"" "$HOME/.bashrc" 2>/dev/null; then
|
|
info "Adding Zsh auto-switch to .bashrc"
|
|
cat >> "$HOME/.bashrc" <<EOF
|
|
|
|
# Auto-switch to zsh if available and interactive
|
|
if [ -t 1 ] && [ -n "\$PS1" ] && [ "\$BASH" ] && [ -x "$ZSH_PATH" ]; then
|
|
export SHELL="$ZSH_PATH"
|
|
exec "$ZSH_PATH"
|
|
fi
|
|
EOF
|
|
fi
|
|
fi
|
|
|
|
info "Installation complete! Please restart your terminal or run 'source $HOME/.zshrc'"
|