start 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. #!/bin/bash
  2. # Dotfiles sync script
  3. set -euo pipefail
  4. # Configuration
  5. checkout_dir=$HOME/.dofiles
  6. script_dir=$checkout_dir/dotfiles
  7. dots_dir=$checkout_dir/dotfiles/dots
  8. repo="https://git.capella.pro/capella/dotfiles.git"
  9. ProgName=$(basename "$0")
  10. # Colors for output
  11. RED='\033[0;31m'
  12. GREEN='\033[0;32m'
  13. YELLOW='\033[1;33m'
  14. NC='\033[0m' # No Color
  15. # Logging functions
  16. log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
  17. log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
  18. log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
  19. # Ensure we cleanup on exit
  20. trap 'cd "$HOME"' EXIT
  21. # Create checkout directory if needed
  22. mkdir -p "$checkout_dir"
  23. sub_help(){
  24. cat << EOF
  25. Usage: $ProgName <subcommand> [options]
  26. Subcommands:
  27. sync [dot_file_to_add] Sync dotfiles (optionally add a new dotfile to track)
  28. list List currently synced dotfiles
  29. status Show git status of dotfiles repo
  30. For help with each subcommand run:
  31. $ProgName <subcommand> -h|--help
  32. EOF
  33. }
  34. sub_sync(){
  35. # Clone repo if it doesn't exist
  36. if [[ ! -d "$checkout_dir/dotfiles/.git" ]]; then
  37. log_info "Cloning dotfiles repository..."
  38. cd "$checkout_dir"
  39. if ! git clone "$repo"; then
  40. log_error "Failed to clone repository"
  41. exit 1
  42. fi
  43. cd "$script_dir"
  44. git submodule init
  45. git submodule update
  46. fi
  47. # Navigate to script directory
  48. cd "$script_dir"
  49. # Get current branch
  50. current_branch=$(git branch --show-current)
  51. log_info "Pulling latest changes from $current_branch..."
  52. git pull origin "$current_branch" || log_warn "Git pull failed, continuing anyway"
  53. git submodule update --init --recursive
  54. # Add new dotfile if provided
  55. if [ -n "${1:-}" ]; then
  56. local file_to_add="$1"
  57. # Validate that file exists
  58. if [[ ! -e "$HOME/$file_to_add" ]]; then
  59. log_error "File $HOME/$file_to_add does not exist"
  60. exit 1
  61. fi
  62. log_info "Adding new dotfile: $file_to_add"
  63. real_path=$(realpath "$HOME/$file_to_add")
  64. to_copy=${real_path#"$HOME/"}
  65. # Create parent directory structure in dots_dir
  66. mkdir -p "$(dirname "$dots_dir/$to_copy")"
  67. # Copy to dots directory
  68. if cp -R "$real_path" "$dots_dir/$to_copy"; then
  69. log_info "Copied $file_to_add to $dots_dir/$to_copy"
  70. else
  71. log_error "Failed to copy $file_to_add"
  72. exit 1
  73. fi
  74. # Add to to_sync file if not already there
  75. if ! grep -qxF "$to_copy" "$script_dir/to_sync" 2>/dev/null; then
  76. echo "$to_copy" >> "$script_dir/to_sync"
  77. log_info "Added $to_copy to sync list"
  78. else
  79. log_info "$to_copy already in sync list"
  80. fi
  81. # Remove original and create symlink
  82. rm -rf "$real_path"
  83. ln -s "$dots_dir/$to_copy" "$real_path"
  84. log_info "Created symlink: $real_path -> $dots_dir/$to_copy"
  85. fi
  86. # Add tracked files to git
  87. log_info "Staging changes..."
  88. while IFS= read -r p; do
  89. [[ -z "$p" ]] && continue # Skip empty lines
  90. file="$dots_dir/$p"
  91. if [[ -e "$file" ]]; then
  92. git add "$file"
  93. else
  94. log_warn "Tracked file $file does not exist"
  95. fi
  96. done < "$script_dir/to_sync"
  97. git add start to_sync
  98. # Commit and push if there are changes
  99. if git diff --cached --quiet; then
  100. log_info "No changes to commit"
  101. else
  102. log_info "Committing changes..."
  103. if git commit -m "Sync: $(date '+%Y-%m-%d %H:%M:%S')"; then
  104. log_info "Pushing to remote..."
  105. git remote add origin "$repo" 2>/dev/null || true
  106. if git push -u origin "$current_branch"; then
  107. log_info "Successfully pushed changes"
  108. else
  109. log_error "Failed to push changes"
  110. exit 1
  111. fi
  112. else
  113. log_error "Failed to commit changes"
  114. exit 1
  115. fi
  116. fi
  117. # Create symlinks for all tracked dotfiles
  118. log_info "Creating symlinks..."
  119. while IFS= read -r p; do
  120. [[ -z "$p" ]] && continue # Skip empty lines
  121. destination="$HOME/$p"
  122. source="$dots_dir/$p"
  123. if [[ ! -e "$source" ]]; then
  124. log_warn "Source file $source does not exist, skipping"
  125. continue
  126. fi
  127. # Check if destination is already a correct symlink
  128. if [[ -L "$destination" ]] && [[ "$(readlink "$destination")" == "$source" ]]; then
  129. continue # Already correctly linked
  130. fi
  131. # Remove destination if it exists
  132. if [[ -e "$destination" ]] || [[ -L "$destination" ]]; then
  133. log_warn "Removing existing $destination"
  134. rm -rf "$destination"
  135. fi
  136. # Create parent directory if needed
  137. mkdir -p "$(dirname "$destination")"
  138. # Create symlink
  139. if ln -s "$source" "$destination"; then
  140. log_info "Linked: $destination -> $source"
  141. else
  142. log_error "Failed to create symlink for $p"
  143. fi
  144. done < "$script_dir/to_sync"
  145. log_info "Sync complete!"
  146. }
  147. sub_list(){
  148. if [[ ! -f "$script_dir/to_sync" ]]; then
  149. log_error "No to_sync file found"
  150. exit 1
  151. fi
  152. echo "Currently synced dotfiles:"
  153. while IFS= read -r p; do
  154. [[ -z "$p" ]] && continue
  155. if [[ -L "$HOME/$p" ]]; then
  156. echo " ✓ $p"
  157. else
  158. echo " ✗ $p (not linked)"
  159. fi
  160. done < "$script_dir/to_sync"
  161. }
  162. sub_status(){
  163. if [[ ! -d "$script_dir/.git" ]]; then
  164. log_error "Dotfiles repository not found"
  165. exit 1
  166. fi
  167. cd "$script_dir"
  168. log_info "Git status:"
  169. git status
  170. }
  171. subcommand=${1:-}
  172. case $subcommand in
  173. "" | "-h" | "--help")
  174. sub_help
  175. ;;
  176. sync|list|status)
  177. shift
  178. "sub_${subcommand}" "$@"
  179. ;;
  180. *)
  181. log_error "'$subcommand' is not a known subcommand."
  182. echo " Run '$ProgName --help' for a list of known subcommands." >&2
  183. exit 1
  184. ;;
  185. esac