diff --git a/README.md b/README.md index bdad7c8..1eabd57 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,48 @@ whenever I have to. I mostly try to keep configuration files as minimalistic as possible in order to make things easier and not to pollute the environment. + +## Installation + +To deploy these dotfiles to your system, use the automated deployment script: + +```bash +./deploy.sh +``` + +This will create symlinks from the repository to your `$HOME` directory. Any existing files will be automatically backed up with a `.backup.TIMESTAMP` suffix. + +### Preview Changes (Dry Run) + +To see what would be deployed without making any changes: + +```bash +./deploy.sh --dry-run +``` + +### Uninstall + +To remove the symlinks created by the deployment script: + +```bash +./uninstall.sh +``` + +To remove symlinks and restore your backed-up files: + +```bash +./uninstall.sh --restore +``` + +## What Gets Deployed + +The deployment script creates symlinks for: + +- **Root-level dotfiles**: `.profile`, `.zshrc` +- **Vim configuration**: `.vim/` +- **Application configs**: `.config/alacritty/`, `.config/clangd/`, `.config/git/`, `.config/nvim/`, `.config/tmux/` + +## Requirements + +- Bash shell +- `readlink` command (usually pre-installed on most Linux distributions) diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..de6ac6c --- /dev/null +++ b/deploy.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# +# Dotfiles deployment script +# +# This script creates symlinks from the repository to $HOME, making it easy to +# keep your dotfiles synchronized across multiple machines. +# + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get the absolute path to the dotfiles directory +DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Function to print colored messages +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to create a backup of an existing file or directory +backup_file() { + local file="$1" + local backup + backup="${file}.backup.$(date +%Y%m%d_%H%M%S)" + + if [ -e "$file" ] && [ ! -L "$file" ]; then + mv "$file" "$backup" + print_warning "Backed up existing $file to $backup" + return 0 + fi + return 1 +} + +# Function to create a symlink +create_symlink() { + local source="$1" + local target="$2" + + # If target is a symlink pointing to the correct source, skip + if [ -L "$target" ] && [ "$(readlink -f "$target")" = "$(readlink -f "$source")" ]; then + print_info "Already linked: $target -> $source" + return 0 + fi + + # If target exists but is not the correct symlink, back it up + if [ -e "$target" ] || [ -L "$target" ]; then + backup_file "$target" + fi + + # Create parent directory if it doesn't exist + local target_dir + target_dir=$(dirname "$target") + if [ ! -d "$target_dir" ]; then + mkdir -p "$target_dir" + print_info "Created directory: $target_dir" + fi + + # Create the symlink + if ! ln -s "$source" "$target" 2>/dev/null; then + print_error "Failed to create symlink: $target -> $source" + return 1 + fi + print_success "Linked: $target -> $source" +} + +# Main deployment function +deploy_dotfiles() { + print_info "Starting dotfiles deployment from $DOTFILES_DIR" + echo "" + + # Deploy root-level dotfiles + print_info "Deploying root-level dotfiles..." + create_symlink "$DOTFILES_DIR/.profile" "$HOME/.profile" + create_symlink "$DOTFILES_DIR/.zshrc" "$HOME/.zshrc" + echo "" + + # Deploy .vim directory + print_info "Deploying .vim configuration..." + create_symlink "$DOTFILES_DIR/.vim" "$HOME/.vim" + echo "" + + # Deploy .config subdirectories + print_info "Deploying .config subdirectories..." + for config_dir in "$DOTFILES_DIR/.config"/*; do + if [ -d "$config_dir" ]; then + local dir_name + dir_name=$(basename "$config_dir") + create_symlink "$config_dir" "$HOME/.config/$dir_name" + fi + done + echo "" + + print_success "Dotfiles deployment completed successfully!" + echo "" + print_info "Note: Any existing files have been backed up with a .backup.TIMESTAMP suffix." +} + +# Function to show usage +show_usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +Automated dotfiles deployment script. + +OPTIONS: + -h, --help Show this help message + -d, --dry-run Show what would be done without making changes + +DESCRIPTION: + This script creates symlinks from the dotfiles repository to your home + directory. Any existing files will be backed up automatically. + +EXAMPLES: + # Deploy dotfiles + ./deploy.sh + + # See what would be deployed without making changes + ./deploy.sh --dry-run + +EOF +} + +# Parse command line arguments +DRY_RUN=false +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -d|--dry-run) + DRY_RUN=true + shift + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Dry run mode +if [ "$DRY_RUN" = true ]; then + print_info "DRY RUN MODE - No changes will be made" + echo "" + print_info "The following symlinks would be created:" + echo "" + echo " $HOME/.profile -> $DOTFILES_DIR/.profile" + echo " $HOME/.zshrc -> $DOTFILES_DIR/.zshrc" + echo " $HOME/.vim -> $DOTFILES_DIR/.vim" + for config_dir in "$DOTFILES_DIR/.config"/*; do + if [ -d "$config_dir" ]; then + dir_name=$(basename "$config_dir") + echo " $HOME/.config/$dir_name -> $config_dir" + fi + done + echo "" + print_info "Run without --dry-run to perform the deployment" + exit 0 +fi + +# Run the deployment +deploy_dotfiles diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..5952b1b --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,209 @@ +#!/bin/bash +# +# Dotfiles uninstall script +# +# This script removes symlinks created by deploy.sh and optionally restores +# backups if they exist. +# + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get the absolute path to the dotfiles directory +DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Function to print colored messages +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to remove a symlink if it points to our dotfiles +remove_symlink() { + local target="$1" + local expected_source="$2" + + if [ -L "$target" ]; then + local actual_source + local expected_source_abs + actual_source=$(readlink -f "$target") + expected_source_abs=$(readlink -f "$expected_source") + + if [ "$actual_source" = "$expected_source_abs" ]; then + if ! rm "$target" 2>/dev/null; then + print_error "Failed to remove symlink: $target" + return 1 + fi + print_success "Removed symlink: $target" + return 0 + else + print_warning "Skipping $target (points to $actual_source, not our dotfiles)" + return 1 + fi + elif [ -e "$target" ]; then + print_warning "Skipping $target (not a symlink)" + return 1 + else + print_info "Already removed: $target" + return 0 + fi +} + +# Function to find and optionally restore the most recent backup +restore_backup() { + local target="$1" + + # Find the most recent backup (portable across Linux and macOS/BSD) + local latest_backup + # Try GNU stat first, then BSD stat + if stat -c '%Y' "$(dirname "$target")" >/dev/null 2>&1; then + # GNU stat (Linux) + latest_backup=$(find "$(dirname "$target")" -maxdepth 1 -name "$(basename "$target").backup.*" -type f -exec stat -c '%Y %n' {} \; 2>/dev/null | sort -rn | head -n 1 | cut -d' ' -f2-) + else + # BSD stat (macOS) + latest_backup=$(find "$(dirname "$target")" -maxdepth 1 -name "$(basename "$target").backup.*" -type f -exec stat -f '%m %N' {} \; 2>/dev/null | sort -rn | head -n 1 | cut -d' ' -f2-) + fi + + if [ -n "$latest_backup" ] && [ -e "$latest_backup" ]; then + print_info "Found backup: $latest_backup" + + if [ "$RESTORE_BACKUPS" = true ]; then + mv "$latest_backup" "$target" + print_success "Restored backup: $latest_backup -> $target" + else + print_info "Use --restore to restore this backup" + fi + fi +} + +# Main uninstall function +uninstall_dotfiles() { + print_info "Starting dotfiles uninstall" + echo "" + + # Remove root-level dotfiles + print_info "Removing root-level dotfiles..." + remove_symlink "$HOME/.profile" "$DOTFILES_DIR/.profile" && restore_backup "$HOME/.profile" + remove_symlink "$HOME/.zshrc" "$DOTFILES_DIR/.zshrc" && restore_backup "$HOME/.zshrc" + echo "" + + # Remove .vim directory + print_info "Removing .vim configuration..." + remove_symlink "$HOME/.vim" "$DOTFILES_DIR/.vim" && restore_backup "$HOME/.vim" + echo "" + + # Remove .config subdirectories + print_info "Removing .config subdirectories..." + for config_dir in "$DOTFILES_DIR/.config"/*; do + if [ -d "$config_dir" ]; then + local dir_name + local target + dir_name=$(basename "$config_dir") + target="$HOME/.config/$dir_name" + remove_symlink "$target" "$config_dir" && restore_backup "$target" + fi + done + echo "" + + print_success "Dotfiles uninstall completed!" + + if [ "$RESTORE_BACKUPS" != true ]; then + echo "" + print_info "Backup files were not restored. Use --restore to restore them." + fi +} + +# Function to show usage +show_usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +Remove dotfiles symlinks created by deploy.sh. + +OPTIONS: + -h, --help Show this help message + -r, --restore Restore backup files after removing symlinks + -d, --dry-run Show what would be done without making changes + +DESCRIPTION: + This script removes symlinks created by deploy.sh. Only symlinks that point + to this dotfiles repository will be removed. Use --restore to automatically + restore the most recent backup files. + +EXAMPLES: + # Remove dotfiles symlinks + ./uninstall.sh + + # Remove symlinks and restore backups + ./uninstall.sh --restore + + # See what would be removed without making changes + ./uninstall.sh --dry-run + +EOF +} + +# Parse command line arguments +DRY_RUN=false +RESTORE_BACKUPS=false +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -r|--restore) + RESTORE_BACKUPS=true + shift + ;; + -d|--dry-run) + DRY_RUN=true + shift + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Dry run mode +if [ "$DRY_RUN" = true ]; then + print_info "DRY RUN MODE - No changes will be made" + echo "" + print_info "The following symlinks would be removed (if they exist and point to our dotfiles):" + echo "" + echo " $HOME/.profile" + echo " $HOME/.zshrc" + echo " $HOME/.vim" + for config_dir in "$DOTFILES_DIR/.config"/*; do + if [ -d "$config_dir" ]; then + dir_name=$(basename "$config_dir") + echo " $HOME/.config/$dir_name" + fi + done + echo "" + print_info "Run without --dry-run to perform the uninstall" + exit 0 +fi + +# Run the uninstall +uninstall_dotfiles