#!/bin/bash # Dotfiles Reset Script # Restore system to plain vanilla state set -e DOTFILES_DIR="$HOME/.dotfiles" RESET_LOG="$DOTFILES_DIR/.reset.log" BACKUP_SUFFIX="_dotfiles_backup_$(date +%Y%m%d_%H%M%S)" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Function to log reset operations reset_log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$RESET_LOG" } # Function to print colored output print_color() { local color=$1 local message=$2 echo -e "${color}${message}${NC}" } # Function to confirm dangerous operations confirm_action() { local message="$1" local default="${2:-n}" echo -e "${YELLOW}${message}${NC}" if [[ "$default" == "y" ]]; then echo "Continue? [Y/n]: " else echo "Continue? [y/N]: " fi read -r response case "$response" in [yY]|[yY][eS]) return 0 ;; [nN]|[nN][oO]) return 1 ;; "") if [[ "$default" == "y" ]]; then return 0 else return 1 fi ;; *) return 1 ;; esac } # Function to backup current dotfiles backup_dotfiles() { print_color "$BLUE" "📦 Creating backup of current dotfiles..." local backup_dir="$HOME/dotfiles_backup_$(date +%Y%m%d_%H%M%S)" mkdir -p "$backup_dir" # Backup dotfiles directory itself if [[ -d "$DOTFILES_DIR" ]]; then cp -r "$DOTFILES_DIR" "$backup_dir/" reset_log "Backed up dotfiles directory to: $backup_dir" fi # Backup symlinked configs local configs=(".bashrc" ".bash_profile" ".zshrc" ".gitconfig" ".vimrc") for config in "${configs[@]}"; do if [[ -L "$HOME/$config" ]]; then # It's a symlink, save the target local target=$(readlink "$HOME/$config") echo "$target" > "$backup_dir/${config}_symlink_target" reset_log "Recorded symlink target for $config: $target" elif [[ -f "$HOME/$config" ]]; then # It's a regular file, copy it cp "$HOME/$config" "$backup_dir/" reset_log "Backed up file: $config" fi done print_color "$GREEN" "✅ Backup created at: $backup_dir" echo "$backup_dir" > "$DOTFILES_DIR/.last_backup_location" } # Function to remove symlinks remove_symlinks() { print_color "$BLUE" "🔗 Removing dotfiles symlinks..." local configs=(".bashrc" ".bash_profile" ".zshrc" ".gitconfig" ".vimrc") for config in "${configs[@]}"; do if [[ -L "$HOME/$config" ]]; then local target=$(readlink "$HOME/$config") if [[ "$target" == *".dotfiles"* ]]; then rm "$HOME/$config" print_color "$CYAN" " Removed symlink: $config" reset_log "Removed symlink: $config -> $target" else print_color "$YELLOW" " Skipped $config (not a dotfiles symlink)" fi elif [[ -f "$HOME/$config" ]]; then print_color "$YELLOW" " $config exists but is not a symlink (keeping as-is)" fi done } # Function to restore original configs restore_original_configs() { print_color "$BLUE" "📄 Restoring original configuration files..." # Look for backup directories created by install.sh local backup_dirs=("$HOME"/.dotfiles_backup_*) local latest_backup="" # Find the most recent backup for backup_dir in "${backup_dirs[@]}"; do if [[ -d "$backup_dir" ]] && [[ "$backup_dir" > "$latest_backup" ]]; then latest_backup="$backup_dir" fi done if [[ -n "$latest_backup" && -d "$latest_backup" ]]; then print_color "$CYAN" " Found backup directory: $latest_backup" # Restore backed up files local configs=(".bashrc" ".bash_profile" ".zshrc" ".gitconfig" ".vimrc") for config in "${configs[@]}"; do if [[ -f "$latest_backup/$config" ]]; then cp "$latest_backup/$config" "$HOME/" print_color "$GREEN" " ✅ Restored: $config" reset_log "Restored original: $config" fi done else print_color "$YELLOW" " No backup directory found. Creating minimal configs..." create_minimal_configs fi } # Function to create minimal default configs create_minimal_configs() { print_color "$BLUE" "🔧 Creating minimal default configurations..." # Create minimal .bashrc if [[ ! -f "$HOME/.bashrc" ]]; then cat > "$HOME/.bashrc" << 'EOF' # ~/.bashrc: executed by bash(1) for non-login shells. # If not running interactively, don't do anything case $- in *i*) ;; *) return;; esac # Basic history settings HISTCONTROL=ignoreboth HISTSIZE=1000 HISTFILESIZE=2000 shopt -s histappend # Check window size after each command shopt -s checkwinsize # Make less more friendly for non-text input files [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" # Set a fancy prompt (color, if available) if [ "$color_prompt" = yes ]; then PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' else PS1='\u@\h:\w\$ ' fi # Enable color support of ls and add handy aliases if [ -x /usr/bin/dircolors ]; then test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" alias ls='ls --color=auto' alias grep='grep --color=auto' fi # Some more ls aliases alias ll='ls -alF' alias la='ls -A' alias l='ls -CF' EOF print_color "$GREEN" " ✅ Created minimal .bashrc" reset_log "Created minimal .bashrc" fi # Create minimal .bash_profile if [[ ! -f "$HOME/.bash_profile" ]]; then cat > "$HOME/.bash_profile" << 'EOF' # ~/.bash_profile: executed by bash(1) for login shells. # Source .bashrc if it exists if [ -f ~/.bashrc ]; then . ~/.bashrc fi EOF print_color "$GREEN" " ✅ Created minimal .bash_profile" reset_log "Created minimal .bash_profile" fi # Create minimal .gitconfig (if git is installed) if command -v git >/dev/null 2>&1 && [[ ! -f "$HOME/.gitconfig" ]]; then cat > "$HOME/.gitconfig" << 'EOF' [user] name = Your Name email = your.email@example.com [core] editor = vim [color] ui = auto EOF print_color "$GREEN" " ✅ Created minimal .gitconfig" reset_log "Created minimal .gitconfig" fi # Create minimal .vimrc (if vim is installed) if command -v vim >/dev/null 2>&1 && [[ ! -f "$HOME/.vimrc" ]]; then cat > "$HOME/.vimrc" << 'EOF' " Basic vim configuration syntax on set number set autoindent set tabstop=4 set shiftwidth=4 set expandtab EOF print_color "$GREEN" " ✅ Created minimal .vimrc" reset_log "Created minimal .vimrc" fi } # Function to uninstall packages uninstall_packages() { print_color "$BLUE" "📦 Uninstalling dotfiles packages..." # Source package manager to get current profile packages if [[ -f "$DOTFILES_DIR/lib/package_manager.sh" ]] && [[ -f "$DOTFILES_DIR/lib/profile_manager.sh" ]]; then source "$DOTFILES_DIR/lib/profile_manager.sh" source "$DOTFILES_DIR/lib/package_manager.sh" local current_profile=$(get_current_profile) print_color "$CYAN" " Current profile: $current_profile" # Uninstall npm packages print_color "$CYAN" " Uninstalling npm packages..." local npm_packages="claude-code gemini-cli task-master-ai pm2" for package in $npm_packages; do if command -v npm >/dev/null 2>&1; then case "$package" in "claude-code") cmd_check="claude" ;; "gemini-cli") cmd_check="gemini" ;; "task-master-ai") cmd_check="task-master" ;; "pm2") cmd_check="pm2" ;; esac if command -v "$cmd_check" >/dev/null 2>&1; then if npm uninstall -g "$package" >/dev/null 2>&1; then print_color "$GREEN" " ✅ Uninstalled: $package" reset_log "Uninstalled npm package: $package" else print_color "$YELLOW" " ⚠️ Failed to uninstall: $package" fi fi fi done # Remove GitHub packages print_color "$CYAN" " Removing GitHub packages..." local github_tools=("fzf" "bat" "rg" "fd" "delta") for tool in "${github_tools[@]}"; do # Remove from ~/.local/bin if [[ -f "$HOME/.local/bin/$tool" ]]; then rm "$HOME/.local/bin/$tool" print_color "$GREEN" " ✅ Removed: $tool" reset_log "Removed GitHub tool: $tool" fi done # Remove fzf installation if [[ -d "$HOME/.fzf" ]]; then if confirm_action "Remove fzf installation directory (~/.fzf)?"; then rm -rf "$HOME/.fzf" print_color "$GREEN" " ✅ Removed: ~/.fzf" reset_log "Removed fzf directory" fi fi # Remove zsh plugins if [[ -d "$HOME/.oh-my-zsh" ]]; then print_color "$CYAN" " Removing custom zsh plugins..." local plugins_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins" local plugins=("zsh-syntax-highlighting" "zsh-autosuggestions") for plugin in "${plugins[@]}"; do if [[ -d "$plugins_dir/$plugin" ]]; then rm -rf "$plugins_dir/$plugin" print_color "$GREEN" " ✅ Removed: $plugin" reset_log "Removed zsh plugin: $plugin" fi done fi else print_color "$YELLOW" " Package manager not available, skipping automatic package removal" fi } # Function to clean up dotfiles directory cleanup_dotfiles_directory() { if confirm_action "🗑️ Remove entire dotfiles directory ($DOTFILES_DIR)?"; then # Move instead of delete for safety local moved_dir="$HOME/dotfiles_removed_$(date +%Y%m%d_%H%M%S)" mv "$DOTFILES_DIR" "$moved_dir" print_color "$GREEN" "✅ Dotfiles directory moved to: $moved_dir" print_color "$CYAN" " (You can safely delete this later if you're sure)" reset_log "Moved dotfiles directory to: $moved_dir" else print_color "$YELLOW" "Keeping dotfiles directory (disabled only)" fi } # Function to display help show_help() { echo "Dotfiles Reset Script" echo "====================" echo "" echo "Options:" echo " --help, -h Show this help message" echo " --soft Soft reset (remove symlinks, restore configs)" echo " --hard Hard reset (soft + uninstall packages)" echo " --nuclear Nuclear reset (hard + remove dotfiles directory)" echo " --backup-only Create backup without making changes" echo "" echo "Reset levels:" echo " Soft: Remove symlinks, restore original configs" echo " Hard: Soft + uninstall dotfiles packages" echo " Nuclear: Hard + remove dotfiles directory entirely" echo "" } # Main execution main() { local reset_type="interactive" # Parse command line arguments case "${1:-}" in --help|-h) show_help exit 0 ;; --soft) reset_type="soft" ;; --hard) reset_type="hard" ;; --nuclear) reset_type="nuclear" ;; --backup-only) reset_type="backup" ;; esac # Create log file touch "$RESET_LOG" reset_log "Starting dotfiles reset (type: $reset_type)" print_color "$RED" "🔄 DOTFILES RESET SCRIPT" print_color "$RED" "=======================" echo "" if [[ "$reset_type" == "interactive" ]]; then print_color "$YELLOW" "Choose reset level:" echo "1) Soft Reset - Remove symlinks, restore original configs" echo "2) Hard Reset - Soft + uninstall packages" echo "3) Nuclear Reset - Hard + remove dotfiles directory" echo "4) Backup Only - Create backup without changes" echo "5) Cancel" echo "" echo "Enter choice [1-5]: " read -r choice case "$choice" in 1) reset_type="soft" ;; 2) reset_type="hard" ;; 3) reset_type="nuclear" ;; 4) reset_type="backup" ;; 5|*) print_color "$YELLOW" "Reset cancelled" exit 0 ;; esac fi # Always create backup first backup_dotfiles echo "" case "$reset_type" in "backup") print_color "$GREEN" "🎉 Backup completed successfully!" ;; "soft") remove_symlinks echo "" restore_original_configs echo "" print_color "$GREEN" "🎉 Soft reset completed!" print_color "$CYAN" " Dotfiles configurations removed, original configs restored" ;; "hard") remove_symlinks echo "" restore_original_configs echo "" uninstall_packages echo "" print_color "$GREEN" "🎉 Hard reset completed!" print_color "$CYAN" " Configs reset and packages uninstalled" ;; "nuclear") remove_symlinks echo "" restore_original_configs echo "" uninstall_packages echo "" cleanup_dotfiles_directory echo "" print_color "$GREEN" "🎉 Nuclear reset completed!" print_color "$CYAN" " Everything removed - system restored to vanilla state" ;; esac echo "" print_color "$BLUE" "💡 Notes:" echo " • Backup location saved for reference" echo " • You may need to restart your shell" echo " • Check ~/.dotfiles/.reset.log for detailed logs" if [[ "$reset_type" != "nuclear" ]]; then echo " • Run './reset.sh --nuclear' to completely remove dotfiles" fi } # Run main function with all arguments main "$@"