clone of my dotfiles.ssp.sh
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 382 lines 10 kB view raw
1#!/bin/bash 2 3# Safe dotfiles restore script 4# This script provides multiple restoration modes to safely restore dotfiles 5# without overwriting existing configurations 6 7set -e # Exit on any error 8 9# Color codes for output 10RED='\033[0;31m' 11GREEN='\033[0;32m' 12YELLOW='\033[1;33m' 13BLUE='\033[0;34m' 14NC='\033[0m' # No Color 15 16# Configuration 17DOTFILES_DIR="$HOME/dotfiles" # Adjust this path 18BACKUP_DIR="$HOME/.dotfiles_backup_$(date +%Y%m%d_%H%M%S)" 19DRY_RUN=false 20INTERACTIVE=true 21FORCE=false 22 23# Usage function 24usage() { 25 echo "Usage: $0 [OPTIONS]" 26 echo "Options:" 27 echo " -d, --dotfiles-dir DIR Dotfiles directory (default: $DOTFILES_DIR)" 28 echo " -b, --backup-dir DIR Backup directory (default: $BACKUP_DIR)" 29 echo " -n, --dry-run Show what would be done without making changes" 30 echo " -y, --yes Non-interactive mode (assume yes)" 31 echo " -f, --force Force overwrite existing files" 32 echo " -h, --help Show this help" 33 echo "" 34 echo "Restoration modes:" 35 echo " 1. Interactive mode (default): Ask before each file" 36 echo " 2. Backup mode: Backup existing files before restore" 37 echo " 3. Merge mode: Attempt to merge configurations" 38 echo " 4. Symlink mode: Create symlinks instead of copying" 39} 40 41# Parse command line arguments 42while [[ $# -gt 0 ]]; do 43 case $1 in 44 -d|--dotfiles-dir) 45 DOTFILES_DIR="$2" 46 shift 2 47 ;; 48 -b|--backup-dir) 49 BACKUP_DIR="$2" 50 shift 2 51 ;; 52 -n|--dry-run) 53 DRY_RUN=true 54 shift 55 ;; 56 -y|--yes) 57 INTERACTIVE=false 58 shift 59 ;; 60 -f|--force) 61 FORCE=true 62 shift 63 ;; 64 -h|--help) 65 usage 66 exit 0 67 ;; 68 *) 69 echo "Unknown option: $1" 70 usage 71 exit 1 72 ;; 73 esac 74done 75 76# Utility functions 77log_info() { 78 echo -e "${BLUE}[INFO]${NC} $1" 79} 80 81log_success() { 82 echo -e "${GREEN}[SUCCESS]${NC} $1" 83} 84 85log_warning() { 86 echo -e "${YELLOW}[WARNING]${NC} $1" 87} 88 89log_error() { 90 echo -e "${RED}[ERROR]${NC} $1" 91} 92 93ask_user() { 94 if [[ "$INTERACTIVE" == false ]]; then 95 return 0 96 fi 97 98 local prompt="$1" 99 local default="${2:-n}" 100 101 while true; do 102 read -p "$prompt [y/N]: " -n 1 -r 103 echo 104 if [[ $REPLY =~ ^[Yy]$ ]]; then 105 return 0 106 elif [[ $REPLY =~ ^[Nn]$ ]] || [[ -z $REPLY ]]; then 107 return 1 108 else 109 echo "Please answer y or n." 110 fi 111 done 112} 113 114# Create backup directory 115create_backup_dir() { 116 if [[ ! -d "$BACKUP_DIR" ]]; then 117 log_info "Creating backup directory: $BACKUP_DIR" 118 if [[ "$DRY_RUN" == false ]]; then 119 mkdir -p "$BACKUP_DIR" 120 fi 121 fi 122} 123 124# Backup existing file 125backup_file() { 126 local target="$1" 127 local backup_path="$BACKUP_DIR/$(dirname "$target")" 128 129 if [[ -e "$target" ]]; then 130 log_info "Backing up existing file: $target" 131 if [[ "$DRY_RUN" == false ]]; then 132 mkdir -p "$backup_path" 133 cp -r "$target" "$backup_path/" 134 fi 135 return 0 136 fi 137 return 1 138} 139 140# Safe copy function 141safe_copy() { 142 local source="$1" 143 local target="$2" 144 local action="$3" # copy, symlink, or merge 145 146 # Check if source exists 147 if [[ ! -e "$source" ]]; then 148 log_warning "Source file does not exist: $source" 149 return 1 150 fi 151 152 # Create target directory if it doesn't exist 153 local target_dir="$(dirname "$target")" 154 if [[ ! -d "$target_dir" ]]; then 155 log_info "Creating directory: $target_dir" 156 if [[ "$DRY_RUN" == false ]]; then 157 mkdir -p "$target_dir" 158 fi 159 fi 160 161 # Handle existing target 162 if [[ -e "$target" ]]; then 163 if [[ "$FORCE" == true ]]; then 164 log_info "Force overwriting: $target" 165 backup_file "$target" 166 elif ask_user "File exists: $target. Overwrite?"; then 167 backup_file "$target" 168 else 169 log_info "Skipping: $target" 170 return 0 171 fi 172 fi 173 174 # Perform the action 175 case "$action" in 176 copy) 177 log_info "Copying: $source -> $target" 178 if [[ "$DRY_RUN" == false ]]; then 179 cp -r "$source" "$target" 180 fi 181 ;; 182 symlink) 183 log_info "Symlinking: $source -> $target" 184 if [[ "$DRY_RUN" == false ]]; then 185 ln -sf "$source" "$target" 186 fi 187 ;; 188 merge) 189 log_info "Merging: $source -> $target" 190 # This is a placeholder - implement specific merge logic per file type 191 if [[ "$DRY_RUN" == false ]]; then 192 cp -r "$source" "$target" 193 fi 194 ;; 195 esac 196} 197 198# Platform detection 199detect_platform() { 200 if [[ "$OSTYPE" == "linux-gnu"* ]]; then 201 echo "linux" 202 elif [[ "$OSTYPE" == "darwin"* ]]; then 203 echo "macos" 204 else 205 echo "unknown" 206 fi 207} 208 209# Adjust paths for different platforms 210adjust_path() { 211 local original_path="$1" 212 local platform="$2" 213 214 case "$platform" in 215 linux) 216 # Convert macOS paths to Linux equivalents 217 echo "$original_path" | sed \ 218 -e 's|~/Library/ApplicationSupport/Code/User/|~/.config/Code/User/|g' \ 219 -e 's|~/Library/Application Support/lazygit/|~/.config/lazygit/|g' 220 ;; 221 macos) 222 # Convert Linux paths to macOS equivalents 223 echo "$original_path" | sed \ 224 -e 's|~/.config/Code/User/|~/Library/ApplicationSupport/Code/User/|g' \ 225 -e 's|~/.config/lazygit/|~/Library/Application Support/lazygit/|g' 226 ;; 227 *) 228 echo "$original_path" 229 ;; 230 esac 231} 232 233# Main restoration function 234restore_dotfiles() { 235 local platform=$(detect_platform) 236 local mode="$1" 237 238 log_info "Detected platform: $platform" 239 log_info "Restoration mode: $mode" 240 241 if [[ "$DRY_RUN" == true ]]; then 242 log_warning "DRY RUN MODE - No changes will be made" 243 fi 244 245 create_backup_dir 246 247 # Define file mappings (source -> target) 248 declare -A file_mappings=( 249 # VS Code 250 ["vscode/settings.json"]="$(adjust_path "~/Library/ApplicationSupport/Code/User/settings.json" "$platform")" 251 ["vscode/keybindings.json"]="$(adjust_path "~/Library/ApplicationSupport/Code/User/keybindings.json" "$platform")" 252 253 # Neovim 254 ["nvim/init.vim"]="~/.config/nvim/init.vim" 255 ["nvim-wp/init.vim"]="~/.config/nvim-wp/init.vim" 256 257 # Tmux 258 ["tmux/tmux.conf"]="~/.tmux.conf" 259 ["tmux/ide"]="~/.tmux/ide" 260 261 # Zsh 262 ["zsh/zshrc"]="~/.zshrc" 263 ["zsh/.secrets"]="~/.dotfiles/zsh/.secrets" 264 ["zsh/aliases.shrc"]="~/.dotfiles/zsh/aliases.shrc" 265 266 # Kitty 267 ["kitty/kitty.conf"]="~/.config/kitty/kitty.conf" 268 ["kitty/gruvbox-kitty.conf"]="~/.config/kitty/gruvbox-kitty.conf" 269 270 # Ghostty 271 ["ghostty/config"]="~/.config/ghostty/config" 272 273 # Yabai & SKHD (macOS only) 274 ["yabai/yabairc"]="~/.config/yabai/yabairc" 275 ["skhd/skhdrc"]="~/.config/skhd/skhdrc" 276 277 # Helix 278 ["helix/config.toml"]="~/.config/helix/config.toml" 279 280 # Git (be careful with this one) 281 # ["git/gitconfig"]="~/.gitconfig" 282 283 # Ranger 284 ["ranger/rc.conf"]="~/.config/ranger/rc.conf" 285 286 # Yazi 287 ["yazi/yazi.toml"]="~/.config/yazi/yazi.toml" 288 289 # Linting 290 ["linting/pylintrc"]="~/.pylintrc" 291 ["linting/flake8"]="~/.config/flake8" 292 293 # Karabiner (macOS only) 294 ["karabiner/karabiner.json"]="~/.config/karabiner/karabiner.json" 295 296 # Lazygit 297 ["lazygit/config.yml"]="$(adjust_path "~/Library/Application Support/lazygit/config.yml" "$platform")" 298 ) 299 300 # Process each file 301 for source_file in "${!file_mappings[@]}"; do 302 local source_path="$DOTFILES_DIR/$source_file" 303 local target_path="${file_mappings[$source_file]}" 304 305 # Expand tilde 306 target_path="${target_path/#\~/$HOME}" 307 308 # Skip platform-specific files 309 if [[ "$platform" == "linux" ]] && [[ "$source_file" == *"yabai"* || "$source_file" == *"skhd"* || "$source_file" == *"karabiner"* ]]; then 310 log_info "Skipping macOS-specific file: $source_file" 311 continue 312 fi 313 314 case "$mode" in 315 copy) 316 safe_copy "$source_path" "$target_path" "copy" 317 ;; 318 symlink) 319 safe_copy "$source_path" "$target_path" "symlink" 320 ;; 321 merge) 322 safe_copy "$source_path" "$target_path" "merge" 323 ;; 324 esac 325 done 326 327 log_success "Dotfiles restoration completed!" 328 if [[ -d "$BACKUP_DIR" ]] && [[ "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ]]; then 329 log_info "Backup of original files created at: $BACKUP_DIR" 330 fi 331} 332 333# Special handling for sensitive files 334handle_sensitive_files() { 335 local sensitive_files=( 336 ".ssh" 337 ".secrets" 338 "gitconfig" 339 ) 340 341 for file in "${sensitive_files[@]}"; do 342 if ask_user "Restore sensitive file: $file?"; then 343 log_warning "Restoring sensitive file: $file" 344 # Add specific handling here 345 fi 346 done 347} 348 349# Main execution 350main() { 351 log_info "Starting dotfiles restoration" 352 353 # Check if dotfiles directory exists 354 if [[ ! -d "$DOTFILES_DIR" ]]; then 355 log_error "Dotfiles directory not found: $DOTFILES_DIR" 356 exit 1 357 fi 358 359 # Choose restoration mode 360 if [[ "$INTERACTIVE" == true ]]; then 361 echo "Choose restoration mode:" 362 echo "1. Copy files (default)" 363 echo "2. Create symlinks" 364 echo "3. Merge configurations" 365 read -p "Enter choice [1-3]: " -n 1 -r 366 echo 367 368 case "$REPLY" in 369 2) restore_dotfiles "symlink" ;; 370 3) restore_dotfiles "merge" ;; 371 *) restore_dotfiles "copy" ;; 372 esac 373 else 374 restore_dotfiles "copy" 375 fi 376 377 # Handle sensitive files separately 378 handle_sensitive_files 379} 380 381# Run main function 382main "$@"