#!/bin/bash # Dotfiles sync script set -euo pipefail # Configuration checkout_dir=$HOME/.dofiles script_dir=$checkout_dir/dotfiles dots_dir=$checkout_dir/dotfiles/dots repo="https://git.capella.pro/capella/dotfiles.git" ProgName=$(basename "$0") # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Logging functions log_info() { echo -e "${GREEN}[INFO]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; } log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } # Ensure we cleanup on exit trap 'cd "$HOME"' EXIT # Create checkout directory if needed mkdir -p "$checkout_dir" sub_help(){ cat << EOF Usage: $ProgName [options] Subcommands: sync [dot_file_to_add] Sync dotfiles (optionally add a new dotfile to track) list List currently synced dotfiles status Show git status of dotfiles repo For help with each subcommand run: $ProgName -h|--help EOF } sub_sync(){ # Clone repo if it doesn't exist if [[ ! -d "$checkout_dir/dotfiles/.git" ]]; then log_info "Cloning dotfiles repository..." cd "$checkout_dir" if ! git clone "$repo"; then log_error "Failed to clone repository" exit 1 fi cd "$script_dir" git submodule init git submodule update fi # Navigate to script directory cd "$script_dir" # Get current branch current_branch=$(git branch --show-current) log_info "Pulling latest changes from $current_branch..." git pull origin "$current_branch" || log_warn "Git pull failed, continuing anyway" git submodule update --init --recursive # Add new dotfile if provided if [ -n "${1:-}" ]; then local file_to_add="$1" # Validate that file exists if [[ ! -e "$HOME/$file_to_add" ]]; then log_error "File $HOME/$file_to_add does not exist" exit 1 fi log_info "Adding new dotfile: $file_to_add" real_path=$(realpath "$HOME/$file_to_add") to_copy=${real_path#"$HOME/"} # Create parent directory structure in dots_dir mkdir -p "$(dirname "$dots_dir/$to_copy")" # Copy to dots directory if cp -R "$real_path" "$dots_dir/$to_copy"; then log_info "Copied $file_to_add to $dots_dir/$to_copy" else log_error "Failed to copy $file_to_add" exit 1 fi # Add to to_sync file if not already there if ! grep -qxF "$to_copy" "$script_dir/to_sync" 2>/dev/null; then echo "$to_copy" >> "$script_dir/to_sync" log_info "Added $to_copy to sync list" else log_info "$to_copy already in sync list" fi # Remove original and create symlink rm -rf "$real_path" ln -s "$dots_dir/$to_copy" "$real_path" log_info "Created symlink: $real_path -> $dots_dir/$to_copy" fi # Add tracked files to git log_info "Staging changes..." while IFS= read -r p; do [[ -z "$p" ]] && continue # Skip empty lines file="$dots_dir/$p" if [[ -e "$file" ]]; then git add "$file" else log_warn "Tracked file $file does not exist" fi done < "$script_dir/to_sync" git add start to_sync # Commit and push if there are changes if git diff --cached --quiet; then log_info "No changes to commit" else log_info "Committing changes..." if git commit -m "Sync: $(date '+%Y-%m-%d %H:%M:%S')"; then log_info "Pushing to remote..." git remote add origin "$repo" 2>/dev/null || true if git push -u origin "$current_branch"; then log_info "Successfully pushed changes" else log_error "Failed to push changes" exit 1 fi else log_error "Failed to commit changes" exit 1 fi fi # Create symlinks for all tracked dotfiles log_info "Creating symlinks..." while IFS= read -r p; do [[ -z "$p" ]] && continue # Skip empty lines destination="$HOME/$p" source="$dots_dir/$p" if [[ ! -e "$source" ]]; then log_warn "Source file $source does not exist, skipping" continue fi # Check if destination is already a correct symlink if [[ -L "$destination" ]] && [[ "$(readlink "$destination")" == "$source" ]]; then continue # Already correctly linked fi # Remove destination if it exists if [[ -e "$destination" ]] || [[ -L "$destination" ]]; then log_warn "Removing existing $destination" rm -rf "$destination" fi # Create parent directory if needed mkdir -p "$(dirname "$destination")" # Create symlink if ln -s "$source" "$destination"; then log_info "Linked: $destination -> $source" else log_error "Failed to create symlink for $p" fi done < "$script_dir/to_sync" log_info "Sync complete!" } sub_list(){ if [[ ! -f "$script_dir/to_sync" ]]; then log_error "No to_sync file found" exit 1 fi echo "Currently synced dotfiles:" while IFS= read -r p; do [[ -z "$p" ]] && continue if [[ -L "$HOME/$p" ]]; then echo " ✓ $p" else echo " ✗ $p (not linked)" fi done < "$script_dir/to_sync" } sub_status(){ if [[ ! -d "$script_dir/.git" ]]; then log_error "Dotfiles repository not found" exit 1 fi cd "$script_dir" log_info "Git status:" git status } subcommand=${1:-} case $subcommand in "" | "-h" | "--help") sub_help ;; sync|list|status) shift "sub_${subcommand}" "$@" ;; *) log_error "'$subcommand' is not a known subcommand." echo " Run '$ProgName --help' for a list of known subcommands." >&2 exit 1 ;; esac